Monthly Archives: 九月 2016

JAVA内存模型

Java内存模式是理解多线程执行的重要依据。

什么是内存模型,为什么需要它?

先看一张图:

8A628D2C-5E67-4A6B-9A2E-66699729E2D4

比如给一个变量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值的最新数据