二、JMM
2、volatile内存语义
volatile是Java虚拟机提供的轻量级的同步机制。volatile关键字有如下两个作用
-
保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。volatile不能保证原子性
public class VolatileVisibilitySample { volatile boolean initFlag = false; public void save(){ this.initFlag = true; String threadname = Thread.currentThread().getName(); System.out.println("线程:"+threadname+":修改共享变量initFlag"); } public void load(){ String threadname = Thread.currentThread().getName(); while (!initFlag){ //线程在此处空跑,等待initFlag状态改变 } System.out.println("线程:"+threadname+"当前线程嗅探到initFlag的状态的改变"); } public static void main(String[] args){ VolatileVisibilitySample sample = new VolatileVisibilitySample(); Thread threadA = new Thread(()->{ sample.save(); },"threadA"); Thread threadB = new Thread(()->{ sample.load(); },"threadB"); threadB.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } threadA.start(); } } /*并不是说不添加volatile就一定不能被感知,只是说加上volatile能保证马上感知,比如说我不添加volatile关键字,但是我在while (!initFlag){中添加一个sout,或者其他操作同样有可能感知到*/
在线程A改变了initFlag的属性之后,线程B能马上感知
//示例 public class VolatileVisibility { public static volatile int i =0; public static void increase(){ i++; } }
volatile无法保证原子性:
在并发场景下,i变量的任何改变都会立马反应到其他线程中,但是如此存在多条线程同时调用increase()方法的话,就会出现线程安全问题,毕竟i++;操作并不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分两步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败,因此对于increase方法必须使用synchronized修饰,以便保证线程安全,需要注意的是一旦使用synchronized修饰方法后,由于synchronized本身也具备与volatile相同的特性,即可见性,因此在这样种情况下就完全可以省去volatile修饰变量。
-
禁止指令重排序优化。