多线程——线程安全的练习加感悟
模拟抢票系统
有三个类:
Main:
package Test.ThreadSafeSimulation;public class Main {public static void main(String[] args) {RaceTicket raceTicket1 = new RaceTicket();RaceTicket raceTicket2 = new RaceTicket();raceTicket1.setName("线程1");raceTicket2.setName("线程2");raceTicket1.start();raceTicket2.start();}
}
RaceTicket:
package Test.ThreadSafeSimulation;public class RaceTicket extends Thread{//如果想要使用同步方法来起到锁的作用,那么几个线程类就不能继承thread,而应该采用接口,因为通过接口创建一个对象,然后依此创建几个线程,调用同步方法的都是一个,也就是唯一的一个那个接口对象。@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++){System.out.println(Thread.currentThread().getName() + "抢到了第" + simulateBankTicket.ticketCount + "张票");simulateBankTicket.ticketCount ++;if(simulateBankTicket.ticketCount > 100){System.out.println("票已售完!!!");break;}}}
}
simulateBankTicket:
package Test.ThreadSafeSimulation;public class simulateBankTicket {static int ticketCount = 1;}
最终实现效果如下:
票与票之间会出现重复,漏票现象。
原理:这源于java的线程实际上是通过争夺CPU执行权来“摇摆”运行。
例如:
1.在线程1运行到simulateBankTicket.ticketCount ++;的时候,线程2此时抢夺到了CPU执行权,又运行了一次simulateBankTicket.ticketCount ++;此时就会发生漏票现象
2.在线程1运行到System.out.println(Thread.currentThread().getName() + “抢到了第” + simulateBankTicket.ticketCount + “张票”);的时候,线程2此时抢夺到了CPU执行权,又运行了一次。此时就会发生重复情况。
解决方法:锁 同步方法。
锁:
关键字synchronized:
代码格式:
synchronized(随便一个对象,但要保证要唯一){这里面是你想要保护不受别的线程干扰的东西,例如多个线程都要操作的某个变量。
}
括号里面的东西叫锁,它来保证别的线程不要打扰“闭关”的线程。并且这个锁要求对所有的线程都具有约束力,因此它必须要唯一。如果不唯一,造成的后果就是,每一个线程都携带了一把属于自己的锁,这个锁锁不住其他线程,因此没任何卵用。
实现代码如下:
package Test.ThreadSafeSimulation;public class RaceTicket extends Thread{//如果想要使用同步方法来起到锁的作用,那么几个线程类就不能继承thread,而应该采用接口,因为通过接口创建一个对象,然后依此创建几个线程,调用同步方法的都是一个,也就是唯一的一个那个接口对象。@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++){synchronized (simulateBankTicket.object) {System.out.println(Thread.currentThread().getName() + "抢到了第" + simulateBankTicket.ticketCount + "张票");simulateBankTicket.ticketCount++;if (simulateBankTicket.ticketCount > 100) {System.out.println("票已售完!!!");break;}}}}
}
在另一个类里面定义唯一锁:
package Test.ThreadSafeSimulation;public class simulateBankTicket {static Object object = new Object();static int ticketCount = 1;
}
注意这个锁一定不能为空。
实现效果如下:
同步方法:
先如此做:
package Test.ThreadSafeSimulation;public class RaceTicket extends Thread{//如果想要使用同步方法来起到锁的作用,那么几个线程类就不能继承thread,而应该采用接口,因为通过接口创建一个对象,然后依此创建几个线程,调用同步方法的都是一个,也就是唯一的一个那个接口对象。@Overridepublic void run() {lock();}public synchronized void lock(){for(int i = 0 ; i < 100 ; i++){System.out.println(Thread.currentThread().getName() + "抢到了第" + simulateBankTicket.ticketCount + "张票");simulateBankTicket.ticketCount++;if (simulateBankTicket.ticketCount > 100) {System.out.println("票已售完!!!");break;}}}
}
实现效果如下:
发现没解决问题。
原理:同步方法的真实代码:this.lock(); this在这个过程中被隐去了。this指的就是本线程的对象。而对于继承了线程类的RaceTicket创建出来的2个对象,执行到输出语句和增加票数的代码的时候,都是自顾自的在调用lock()方法,本质上和刚才锁的不唯一的出错逻辑一样,都没有办法锁住其他线程。
如何改正?
答案是:
使用接口创建线程对象:
实现代码如下:
package Test.ThreadSafeSimulation;public class Main {public static void main(String[] args) {
// RaceTicket raceTicket1 = new RaceTicket();
// RaceTicket raceTicket2 = new RaceTicket();
// raceTicket1.setName("线程1");
// raceTicket2.setName("线程2");
//
// raceTicket1.start();
// raceTicket2.start();RaceTicket r1 = new RaceTicket();new Thread(r1,"线程1").start();new Thread(r1,"线程2").start();}
}
package Test.ThreadSafeSimulation;public class RaceTicket implements Runnable{//如果想要使用同步方法来起到锁的作用,那么几个线程类就不能继承thread,而应该采用接口,因为通过接口创建一个对象,然后依此创建几个线程,调用同步方法的都是一个,也就是唯一的一个那个接口对象。@Overridepublic void run() {lock();}public synchronized void lock(){for(int i = 0 ; i < 100 ; i++){System.out.println(Thread.currentThread().getName() + "抢到了第" + simulateBankTicket.ticketCount + "张票");simulateBankTicket.ticketCount++;if (simulateBankTicket.ticketCount > 100) {System.out.println("票已售完!!!");break;}}}
}
这也是为什么如果要用同步方法的话,需要使用接口来创建线程对象了,因为只有包装了同一个接口对象的线程,在调用同步方法的时候,可以成功锁住其他线程对象。因为这个接口对象是唯一的。