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值的最新数据
