Java内存模式是理解多线程执行的重要依据。
什么是内存模型,为什么需要它?
先看一张图:
比如给一个变量aVariable赋值:aVariable=3,当然单线程下是没什么问题的,但如果是多线程呢?会不会有影响?答案是肯定的,多线程下数据的读写都会发生竞争。
所以用一句话总结JAVA内存模型是:
通过加锁来控制多线程下共享变量的读写顺序操作
如何保证?JMM定义了一套Happens-Before规则,包括:
程序顺序规则:如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行
监视器锁规则:同一监视器的解锁发生在加锁之前。
volatile变量规则:对volatile变量的写入操作必须在对该变量的读操作之前执行。
线程启动规则:Thread.start的调用必须在线程其他操作之前执行
线程结束规则:线程的任何操作必须在检测到其他线程结束之前执行,或从Thread.join中成功返回,或调用Thread.isAlive时返回false
中断规则:调用interrupt时,必须在中断线程检测到interrupt之前执行(就是先调用了interrupt才会抛出InterruptedException,感觉是废话)
终结器规则:对象的构造函数必须在启动对象的终结器之前执行完成
传递性:如果操作A在操作B之前执行,且操作B在C之前执行,那么A必须在C之前执行
如果程序中不满足以上的情况,那么多线程执行下就会发生重排序,什么是重排序?举个例子,如下:
public class PossibleRecordering { static int x=0,y=0; static int a=0,b=0; public static void main(String[] args) throws InterruptedException { Thread one = new Thread(new Runnable() { @Override public void run() { a = 1; x = b; } }); Thread other = new Thread(new Runnable() { @Override public void run() { b = 1; y = a; } }); one.start();other.start(); one.join(); other.join(); System.out.println("x="+x+",y="+y); } }
上面的例子x和y是多少呢?不确定,可能是0,也可能是1,线程中a=1和x=b两者没有任何依耐性,所以JMM会对这两个操作执行重排,先执行哪个还真不一定。如果想要x=b在a=1之后执行,那么将x用volatile修饰
当使用volatile修饰后,JMM确保
1)当第二个操作是volatile写时,不管第一个是什么,都不能发生重排
2)当第一个操作是volatile读时,不管第二个操作是什么,都不能发生重排
3)当第一个操作为volatile写,第二个是volatile读时,都不能发生重排
为了好记就是:一读二写不能发生重排
final关键字
在构造函数中初始化的使用final修饰的关键字对象,优先于其他线程的读操作
举个例子:
public class SafeStates { private final Map<String, String> states; private int i=0; static SafeStates obj; public SafeStates () { states = new HashMap<String, String>(); states.put("alaska", "AK"); states.put("alabama", "AL"); i=1; } public void addStates() { states.put("add", "AD"); } public static void writer() { obj = new SafeStates(); } public static void reader() { SafeStates o = obj; System.out.println(o.states); System.out.println(o.i); } }
一个线程先调用了writer方法,然后另外一个线程调用reader方法,JMM可确保使用final修饰的变量,的赋值操作在reader方法之前,即reader方法读取到的states对象是{“alaska”, “AK”,alabama”, “AL”},但i值就不可保证了,可能读取的值为0
另外如果在非构造方法中,比如调用了addState方法,其他线程在读取的时候也不能确保读取到states值的最新数据