如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
如下例中,如果有三个线程T0,T1,T2。若ticket只剩最后1张票时,当T0抢到CPU资源时,T0运行if语句判断后休眠;T1又在此时抢到了CPU资源,T1也运行到if语句判断,此时票数仍然为1,因为T0并没有运行到把票数减少的语句,所以T1也在此时休眠·;如果T2又在此时抢到了CPU资源,那么它也会运行到if后,因为此时票数仍然没有减少。
那么问题来了,线程不会走回头路,那么当CPU回过头来再进行这三个线程时,三个线程都可以继续向下执行,但是只剩下了一张票,就会出现0和负数,就出现了错误。
public class Ticket implements Runnable { //共100票 int ticket = 100; @Override public void run() { //模拟卖票 while(true){ if (ticket > 0) { //模拟选坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } }
java中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) { 可能会产生线程安全问题的代码 }
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
使用同步代码块,对电影院卖票案例中Ticket类进行如下代码修改:
public class Ticket implements Runnable { //共100票 int ticket = 100; //定义锁对象 Object lock = new Object(); @Override public void run() { //模拟卖票 while(true){ //同步代码块 synchronized (lock){ if (ticket > 0) { //模拟电影选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } } } }
当线程遇到同步代码块后,线程判断同步锁是否还有,也就是上面代码中的lock,如果有,线程就把锁获取并进入同步中,当执行完同步代码块内的语句并出去同步代码块的时候,线程会将锁对象(也就是上面代码中的lock)还回去。如果线程遇到同步代码块但没有锁对象,那么没有锁的线程不能进入同步中。
加了同步后,导致程序运行速度下降,但在安全性和速度之间应选择安全性。