我们知道java并发编程主要会有可见性,原子性,有序性几个问题。
volatile可以保证可见性和有序性,但是无法保证原子性。
有序性:编译器会根据自认为的排序对代码进行指令重排,指令重排不会对单线程造成影响,但是可能会导致多线程时候运行错误。
可见性:可行性是指对线程的操作对其他线程可见。
volatile关键字可以禁止指令重排,所以可以保证了有序性。有序性也通常是指的happen-before原则。
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
volatile保证可见性主要是通过java内存模型(JMM)来实现。
因为java内存模型中,在使用共享变量时,会有一个主内存,多线程在操作变量时,会复制一份数据到自己的线程的工作内存中去。使用volatile关键字修饰之后,在线程操作完volatile修饰的变量之后,会同步到主内存中去,根据缓存一致性协议以及总线嗅探机制,其余线程中此变量的数据在自己的工作内存中失效,强制从主工作内存中读取,这样就实现了数据和主内存一致,从而达到数据的可见性。
那为什么volatile不能保证原子性呢?
因为在多线程并发的情况下,可能在现在写了数据还没有同步到主内存的时候,其他线程同样进行了操作,比如i++时候,初始时时1,一个线程两个线程同时读取到了1,然后都进行了+1的操作,这样的话数据只会变成了2,并不会变成3,导致了不能保证原子性。