在谈及线程安全时,常会说到一个变量——volatile。在《Java并发编程实战》一书中是这么定义volatile的——Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。书中的这句话说明了两点:①volatile变量是一种稍弱的同步机制;②volatile能够确保将变量的更新操作通知到其他线程——可见性。这两点和我们探讨“volatile变量是否能够保证线程安全性”息息相关。
什么是同步机制?在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步(该定义源于百度百科)。也就是说,同步机制即为对共享资源的一种制约。《Java并发编程实战》中为什么说volatile是一种“稍弱的同步机制”呢?这和volatile能够确保可见性这一重要作用相关:
volatile能够保证字段的可见性:volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
可见性和“每个线程都有自己的缓存(或叫“线程的工作内存”)”有关系:
①操作没有用volatile来修饰字段时,各个线程都是先从主内存(堆)中复制一份数据到自己的工作内存中,然后操作自己工作内存中的数据,最后再更新到主内存中。
②当字段被volatile修饰后,各个线程操作该字段时,都是直接在主内存中进行操作的。
因为volatile能够确保可见性,所以,在一些特定情形下可以使用 volatile 变量替代锁,例如:在直接修改变量(不需先判断再修改)的情况下,多个线程同时去修改某个变量,一旦某个线程操作成功了,其他线程对这个变量的修改就立刻建立在最新的变量值上再进行修改,这样一来就避免了线程安全问题。但是,要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:①对变量的写操作不依赖于当前值;②该变量没有包含在具有其他变量的不变式中;否则依旧会出现线程安全问题。我们用代码来做说明:
class Window implements Runnable { private volatile int ticket = 100; public void run() { for (;;) { //通过下面的①②两个步骤我们可以发现:当不能满足“对变量的操作不依赖与当前值”,自然就会有线程安全问题。 if (ticket > 0) { try { Thread.sleep(100);//①多个线程同时判断到“ticket>0”,然后挂起了 } catch (InterruptedException e) { e.printStackTrace(); } //②多个线程同时醒来,同时进行“ticket--”操作: System.out.println(Thread.currentThread().getName() + ":" + ticket--); } else { break; } } } }