volatile作为一个轻量级锁机制,首先保证了可见性,处理器使用MESI(修改、独占、共享、无效)缓存一致性协议来保证可见性,当多个线程修改数据时,先到达的会使用Lock前缀指令锁住主缓存的缓存行,缓存行就是我们数据存在缓存里的最小单位,修改自己的数据之后,其它线程通过嗅探技术发现主存的数据修改,会无效化自己的缓存,使得下次取数据的时候从主存里取数据
注意两点:
一:happens-before原则:假如一写一读两个线程同时操作数据,写优先
二:两个线程同时写会有伪共享问题
伪共享问题:
当两个线程同时修改同一个缓存行的数据的时候,因为先到达会锁住缓存行,那么第二个到达的依旧会等待,类似于synchronized悲观锁,比如线程A修改缓存行的A数据,线程B修改同一个缓存行的B数据,就会变成串行修改,也就是伪共享问题
怎么解决?
当下采用的多是使不同变量在不同缓存行,即如果数据占不了一个缓存行,那么填充缓存行,把缓存行填满,使不同的数据不再同一个缓存行中,比如@Contended注解,concurrentHashMap就是这样用的,还有Disruptor框架采用填充缓存行的方式,有兴趣的可以去了解下
不保证原子性
对于i++操作,这个i++是分为了三步骤,取值、加、设值,当线程A取得主存的值i=10,线程B取得值为10,线程A加1,设置回主存,线程B也加1,设置回主存,两个线程加完之后,数据依旧为1,所以不保证原子性
特别注意:
很多人对这个可见性有所疑惑,认为线程B修改到主存之后,就会让线程A知道,线程A重新去取值,注意这里并不是,线程A已经取过值之后,其它值修改了主存,他不会去取值,而是自己进行加的操作,这也就是没有原子性的原因,如果线程A会去重新取值的话,那么以新的值来操作,不就是有原子性了吗
volatile的第二个作用是插入内存屏障禁止指令重排序,虽然保证了一些数据安全,但是效率变低