包含可见性,Volatile,线程封闭和安全发布。

1. 可见性

1.1 Volatile 变量

Volatile 变量用来确保将变量的更新操作通知到其他线程。

  1. 无指令重排: 当把变量生命为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
  2. 无寄存器缓存: volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值。

volatile 变量的典型用法:检查某个状态标记以判断是否退出循环。例如:

// asleep 必须为 volatile
// 否则当 asleep 被另一个线程修改时,执行判断的线程却发现不了
volatile boolean asleep;
...
    while(!asleep) {
        countSomeSheep();
    }

volatile 通常用作某个操作完成、发生终端或者状态的标志

加锁机制既可以确保可见性又可以确保原子性,而 volatile 变量只能确保可见性。例如,volatile的语义不足以确保递增操作 (count++) 的原子性。

2. 线程封闭

一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭(Thread Confinement),它是实现线程安全性的最简单方式之一。 当某个对象封闭在一个线程中时,将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

例如:Swing,JDBC的 Connection 对象 (但连接池是线程安全的)

Java提供了一种机制来帮助维持线程封闭性,例如 局部变量ThreadLocal 类。

2.1 栈封闭

意思就是使用局部变量。局部变量位于执行线程的栈中,其他线程无法访问这个栈,自然就线程安全了。
只要确保被引用的对象不会逸出就行了

2.2 ThreadLocal 类

ThreadLocal 类能将值与保存这个值的线程对应起来。ThreadLocal 提供了 get, set 等方法,这些方法能为每个使用该变量的线程都保存一份独立的副本, 因此 get总是返回由当前执行线程 set的值。

当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时重新分配该临时对象时,就可以使用 ThreadLocal。
例如通过将 JDBC 的连接保存到 ThreadLocal 对象中,每个线程都会拥有属于自己的链接:

// 使用 ThreadLocal 类维持线程封闭性
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    return DriverManager.getConnection(DB_URL);
};

public static Connection getConnection() {
    return connectionHolder.get();
}

这些特定于线程的值保存在 Thread 对象中,当线程终止后,这些值会作为垃圾回收。

ThreadLocal 变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性,使用时要小心。

3. 安全发布

看一个不安全的发布:

// 不安全的发布
public Holder holder;

public void initialize() {
    holder = new Holder(42);
}

// 由于未被正确发布,这个类可能出现故障
public class Holder {
    private int n;
    
    public Holder(int n) {
        this.n = n;
    }
    
    public void assertSanity() {
        if(n != n) {
            throw new AssertionError("This statement is false.");
        }
    }   
}

解释:尽管在构造函数中设置的域值似乎是第一次向这些域中写入的值,因此不会有“更旧的”值被视为失效值, 但 Object 的构造函数会在子类构造函数运行之前先将默认值写入所有的域。 因此某个域默认的值可能被视为失效值。

3.1 安全发布的常用模式

要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用。例如:public static Holder holder = new Holder(42); 静态初始化器由 JVM在类的初始化阶段执行。 由于 JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全发布。
  • 将对象的引用保存到 volatile 类型的域或者 AtomicReference 对象中。
  • 将对象的引用保存到某个正确构造对象的 final 类型域中。
  • 将对象的引用保存到一个由锁保护的域中。例如: Vector, synchronizedList。