Java内存模型(JMM)描述了Java程序中变量(线程公用变量)的访问规则(能够看作是一种规范),以及在JVM中将变量存储到内存和内存中读取出变量这样的底层细节。java
而且规定:git
举个例子(修改线程A中的变量):安全
若是知足上面两点,也就是说线程A中更新的共享变量线程B中可以及时获得更新,就称为变量是可见的,反正则是不可见。多线程
as-if-serial
代码书写顺序与代码实际执行的顺序不一样,指令重排序是编译器或处理器为了提升程序性能而作出的优化。并发
主要有三种方式:ide
举个例子:性能
int a = 2; int b = 3; int c = a + b;
在实际运行中多是:优化
int b = 3; int a = 2; int c = a + b;
上述例子中演示了指令重排序,那么可能会有人问,第三行代码若是重排序到前两行代码以前,岂不是会报错吗?this
有一个as-if-serial
,其内容以下:线程
不管如何重排序,程序执行的结果应该与代码顺序执行结果一致。(Java编译器、运行时和处理器都要保证Java在单线程下遵循as-if-serial语义)
所以在单线程的状况下你没必要担忧指令重排序带来什么不良后果。
可是在多线程交错执行时,重排序就可能形成内存可见性问题,详情请继续阅读下文。
package cn.com.dotleo; /** * Created by liufei on 2018/6/16. */ public class SynchronizedDemo { // 共享变量 private boolean ready = false; private int result = 0; private int number = 1; // 写操做 public void write() { ready = true; // 1.1 number = 2; // 1.2 } // 读操做 public void read() { if (ready) { // 2.1 result = number * 3; //2.2 } System.out.println("result的值为:" + result); } private class ReadWriteThread extends Thread { private boolean flag; public ReadWriteThread(boolean flag) { this.flag = flag; } // 根据传入执行不一样的读写操做 @Override public void run() { if (flag) { write(); } else { read(); } } } public static void main(String[] args) { SynchronizedDemo demo = new SynchronizedDemo(); // 启动写线程 demo.new ReadWriteThread(true).start(); // 启动读线程 demo.new ReadWriteThread(false).start(); } }
代码参见SynchronizedDemo
对于这个程序,若是执行main方法将可能有一下几种状况:
其实,2.1和2.2也是能够重排序的:
int temp = number * 3; if (ready) { result = temp; }
所以就有了:
到这里,能够总结一下为何会出现共享变量线程不安全的主要缘由了:
从Java语言层面讲,主要支持一下两种方式:
Synchronized
Volatile
不包括JDK 1.5提供的Java并发包
JMM关于Synchronized
的两条规定:
咱们先来修改一下原来的代码,保证其共享变量在多线程下的可见性。
// 写操做 public synchronized void write() { ready = true; // 1.1 number = 2; // 1.2 } // 读操做 public synchronized void read() { if (ready) { // 2.1 result = number * 3; //2.2 } System.out.println("result的值为:" + result); }
为何这个操做能保证其可见性呢?咱们经过分析致使共享变量线程不可见的3个缘由逐一分析:
Synchronized
关键词加锁后,保证了线程不会交叉执行as-if-serial
语义Synchronized
的两条规定能保证它及时获取而且在操做结束后及时更新主内存中的共享变量的值。关于volatile
,它有如下特性:
它的这些特性是经过内存屏障和禁止指令重排序优化来实现的。
store
屏障指令,让主内存中的变量及时更新load
屏障指令,更新主内存中的变量举一个volatile
的例子说明它不具有原子性。
package cn.com.dotleo; /** * Created by liufei on 2018/6/16. */ public class VolatileDemo { private volatile int num = 0; public int getNum() { return this.num; } public void increase() { this.num++; } public static void main(String[] args) { final VolatileDemo volatileDemo = new VolatileDemo(); for (int i = 0; i < 500; i++) { new Thread(new Runnable() { public void run() { volatileDemo.increase(); } }).start(); } // 为了让全部线程执行完毕 // 若是还有子线程执行 // 主线程让出cpu资源 while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println("num" + volatileDemo.getNum()); } }
代码参见VolatileDemo
该程序的运行结果不老是500。由于increase()
方法中的num++
并不是原子操做,它包括了:
试分析一种状况:
至此,两次循环却少加了,致使并不是咱们想要的num最终 = 500
要在多线程中安全的使用volatile
,必须同时知足: