线程的三大特性
1、 原子性:即一个线程的全部操作是原子性的,要么全部成功,要么全部失败。
2、 可见性:当多个线程同时操作一个数据时,如果其中一个线程对该值进行更新修改,其他的线程能同时对已读的该值进行更新为最新的值。
3、 有序性:一个线程中的操作是有序执行的,不会发生因为指令重排现象而导致得到的数据执行结果不一致。
Synchronized与lock锁都能保证一个时刻一个只有线程能够访问该资源,所以能保证线程的原子性,以及可见性和有序性。
Volatile 关键字可以保证线程的可见性和有序性,但不能保证其原子性。
Volatile关键字解析
Volatile 在汇编指令阶段加上了Lock前缀,然后通过底层的mesi协议来保证线程的可见性,通过内存屏障,禁止指令的重排优化,来保证线程的可见性。
指令重排:
jvm线程内部的执行顺序,只要其运行结果和顺序执行一致,指令的执行顺序可以与顺序执行不一致。c1 = 0; c2 = 0; d1 = 0; d2 = 0; Thread t1 = new Thread(new Runnable() { public void run() { shortWait(10000); d1 = 1; c1 = d2; } }); Thread t2 = new Thread(new Runnable() { public void run() { d2 = 1; c2 = d1; } });
如上所示,最后c1和c2的结果会出现三种情况,但是以上三种情况都是在没有发生指令重排的情况下产生的。
那么问题来了,会不会出现C1和C2都是0的情况?
答案是肯定的。
这种情况就是指令重排造成的。
MESI缓存一致性解析
E:独占状态,只有一个线程访问该资源。
S:共享状态,有一个以上线程数同时访问该资源,此时该值为共享状态。
M:修改状态,如果有一个线程修改了多个线程共享的资源,那么,该线程会把此线程内的该值标记为修改状态,通知,通知其他共享了该值的线程,将其他的线程内的该值变为无效状态(I)。
I:无效状态,将该线程的值标记成无效状态,如果需要使用该值,需要重新到内存读取。
volatile为什么不能保证原子性:
当jvm执行i++操作时,如果将i++读到了寄存器中,此时变量i变成了无效状态,该线程的缓存行会重新从内存读取数据i,但是寄存器中的数据i并不会被更新,所以会导致寄存器中i的数据值不是最新值,则运算后的结果也会有误。
一个缓存行的数据是原子的,如果一个变量太大,一个缓存行放不下,则缓存一致性协议就会无法使用,此时会用弃用缓存一致性协议,使用总线锁。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。