Java多线程发展简史(六)

2014-11-24 09:39:52 · 作者: · 浏览: 11
性内存模型和先行发生模型。
对于连续一致性模型来说,程序执行的顺序和代码上显示的顺序是完全一致的。这对于现代多核,并且指令执行优化的CPU来说,是很难保证的。而且,顺序一致性的保证将JVM对代码的运行期优化严重限制住了。
但是JSR 133指定的先行发生(Happens-before)使得执行指令的顺序变得灵活:
● 在同一个线程里面,按照代码执行的顺序(也就是代码语义的顺序),前一个操作先于后面一个操作发生
● 对一个monitor对象的解锁操作先于后续对同一个monitor对象的锁操作
● 对volatile字段的写操作先于后面的对此字段的读操作
● 对线程的start操作(调用线程对象的start()方法)先于这个线程的其他任何操作
● 一个线程中所有的操作先于其他任何线程在此线程上调用 join()方法
● 如果A操作优先于B,B操作优先于C,那么A操作优先于C
而在内存分配上,将每个线程各自的工作内存(甚至包括)从主存中独立出来,更是给JVM大量的空间来优化线程内指令的执行。主存中的变量可以被拷贝到线程的工作内存中去单独执行,在执行结束后,结果可以在某个时间刷回主存:

但是,怎样来保证各个线程之间数据的一致性?JLS给的办法就是,默认情况下,不能保证任意时刻的数据一致性,但是通过对synchronized、volatile和final这几个语义被增强的关键字的使用,可以做到数据一致性。要解释这个问题,不如看一看经典的DCL(Double Check Lock)问题:

public class DoubleCheckLock {
private volatile static DoubleCheckLock instance; // Do I need add "volatile" here
private final Element element = new Element(); // Should I add "final" here Is a "final" enough here Or I should use "volatile"

private DoubleCheckLock() {
}

public static DoubleCheckLock getInstance() {
if (null == instance)
synchronized (DoubleCheckLock.class) {
if (null == instance)
instance = new DoubleCheckLock();
//the writes which initialize instance and the write to the instance field can be reordered without "volatile"
}

return instance;
}

public Element getElement() {
return element;
}

}

class Element {
public String name = new String("abc");
}
在上面这个例子中,如果不对instance声明的地方使用volatile关键字,JVM将不能保证getInstance方法获取到的instance是一个完整的、正确的instance,而volatile关键字保证了instance的可见性,即能够保证获取到当时真实的instance对象。
但是问题没有那么简单,对于上例中的element而言,如果没有volatile和final修饰,element里的name也无法在前文所述的instance返回给外部时的可见性。如果element是不可变对象,使用final也可以保证它在构造方法调用后的可见性。
对于volatile的效果,很多人都希望有一段简短的代码能够看到,使用volatile和不使用volatile的情况下执行结果的差别。可惜这其实并不好找。这里我给出这样一个不甚严格的例子:

public class Volatile {


public static void main(String[] args) {
final Volatile volObj = new Volatile();
Thread t2 = new Thread() {
public void run() {
while (true) {
volObj.check();
}
}
};
t2.start();
Thread t1 = new Thread() {
public void run() {
while (true) {
volObj.swap();
}
}
};
t1.start();
}

boolean boolValue;// use volatile to print "WTF!"

public void check() {
if (boolValue == !boolValue)
System.out.println("WTF!");
}

public void swap() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolValue = !boolValue;
}

}
代码中存在两个线程,一个线程通过一个死循环不断在变换boolValue的取值;另一个线程每100毫秒执行“boolValue==!boolValue”,这行代码会取两次boolValue,可以想象的是,有一定概率会出现这两次取boolValue结果不一致的情况,那么这个时候就会打印“WTF!”。
但是,上面的情况是对boolValue使用volatile修饰保证其可见性的情况下出现的,如果不对boolValue使用volatile修饰,运行时就一次不会出现(起码在我的电脑上)打印“WTF!”的情形,换句话说,这反而是不太正常的,我无法猜测JVM做了什么操作,基本上唯一可以确定的是,没有用volatile修饰的时候,boolValue在获取的时候,并不能总取到最真实的值。
JSR 166
JSR 166的贡献就是引入了java.util.concurrent这个包。前面曾经讲解过AtomicXXX类这种原子类型,内部实现保证其原子性的其实是通过一个compareAndSet(x,y)方法(CAS),而这个方法追踪到最底层,是通过CPU的一个单独的指令来实现的。这个方法所做的事情,就是保证在某变量取值为x的情况下,将取值x替换为y。在这个过程中,并没有任何加锁的行为,