Skip to main content

第16章 Java内存模型

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

aVariable = 3;

内存模型需要解决这个问题:这什么条件下,读取aVariable的线程看到这个值为3

JMM 规定了 JVM 必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对于其他线程可见。 JMM 在设计时就在可预测性和程序的易于开发性之间进行了权衡,从而旨在各种主流的处理器体系架构上能实现高性能的 JVM

平台的内存模型

在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherenc的,其中一部分只提供最小的保证,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。

重排序

同步将限制编译器、运行时和硬件对内存操作重排序的方式,从而实施重排序时不会破坏 JMM 提供的可见性保证。

Java内存模型简介

JMM 为程序中所有的操作定义了一个偏序关系 ,称之Happens-Before.

  • 程序顺序规则
  • 监视器锁规则
  • volatile 变量规则
  • 线程启动规则
  • 中断规则
  • 终结器规则
  • 传递性

借助同步

由于 Happens-Before 的排序功能很强大,因此有时候可以“借助( Piggyback )"现有同步机制的可见性属性。

程序清单 16-2 说明如何借助同步的 FutureTask 的内部类

// FutureTask 内部类
private final class Sync extends AbstractQueuedSynchronizer {
private static final int RUNNING = 1, RAN = 2, CANCELLED = 4;
private V result;
private Exception exception;

void innerSet(V v) {
while (true) {
int s = getState();
if (ranOrCancelled(s)) {
return;
}
if (compareAndSetState(s, RAN)) {
break;
}
}
result = v;
releaseShared(0);
done();
}

V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED) {
throw new CancellationException();
}
if (exception != null) {
throw new ExecutionException(exception);
}
return result;
}
}

发布

不安全的发布

除了不可变对象以外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行

安全的发布

Happens-Before 排序是在内存访问级别上操作的,它是一种“并发级汇编语言“,而安全发布的运行级别更接近程序设计。

安全初始化模式

线程安全的延迟初始化

@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;

public synchronized static Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return source();
}
}

程序清单 16-5 提前初始化

@ThreadSafe
public class EagerInitialization {
private static Resource resource = new Resource();

public static Resource getResource() {
return resource;
}
}

程序清单 16-6 延长初始化占位类模式

@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceHolder.resource;
}
}

双重检查加锁

DCL 的这种使用方法已经被广泛地废弃了,促使该模式出现的驱动力〈无竞争同步的执行速度很慢,以及 JVM 启动时很慢)己经

不复存在,因而它不是一种高效的优化措施。延迟初始化占位类模式能带来同样的优势,并且更容易理解。

初始化过程中的安全性

初始化安全性将确保,对于被正确构造的对象,所有线程都能看到由构造函数为对象给各个 final 域设置的正确值,而不管采用何种方式来发布对象。而且,对于可以通过被正确构造对象中某个 final 域到达的任意变量(例如某个 final 数组中的元素,或者由一个 final 域引用的 HashMap 的内容)将同样对于其他线程是可见的。

程序清单 16-8 不可变对象的初始化安全性

@ThreadSafe
public class SafeStates {
private final Map<String, String> states;

public SafeStates() {
states = new HashMap<String, String>();
states.put("alaska", "AK");
states.put("alabama", "AL");

}
public String getAbbreviation(String s) {
return states.get(s);
}
}

初始化安全性只能保证通过 final 域可达的值从构造过程完成时开始的可见性。对于通过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性。