Java并发之对象的共享

1 可见性
—synchronized既能用于实现原子性或者确定“临界区”,还能用于实现内存可见性。
—加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

2 发布和逸出
—发布一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。
—逸出:某个不应该被发布的对象被发布。
—发布的形式包括:
(1)将一个指向该对象的引用保存到其他代码可以访问的地方,如下例中的HashSet对象。
Java并发之对象的共享
(2)在某一个非私有的方法中返回该引用,如下例中的states数组。
Java并发之对象的共享
(3)将引用传递到其他类的方法中,如下例中的ThisEscape实例本身。
Java并发之对象的共享
—当发布某个对象时,可能会间接地发布其他对象。当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。一般来说,如果一个已经发布的对象能够通过非私有的变量引用或方法调用到达其他的对象,那么这些对象也都会被发布。
—不要在构造过程中使this引用逸出。如在构造函数中启动一个线程或者在构造函数中调用一个可改写的实例方法(既非private方法,也非final方法)时,都会导致this引用在构造过程中溢出。
—如果想在构造函数中注册一个事件监听器或启动线程,那么可以使用一个私有的构造函数和一个公共的工厂方法来实现。

3 线程封闭
—不共享数据,仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭。
—线程封闭是实现线程安全性的最简单方式之一。
—常见应用:JDBC的Connection对象。
—Java语言及其核心库提供了一些机制来帮助维持线程封闭性,例如局部变量和ThreadLocal类。
(1)Ad-hoc线程封闭
—Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。
—Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,能将对象封闭到目标线程上。
—当决定使用线程封闭技术时,通常是因为要将某个特定的子系统实现为一个单线程子系统。
—在volatile变量上存在一种特殊的线程封闭。当能确保只有单个线程对共享的volatile变量执行写入操作,那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”操作。
(2)栈封闭
—在栈封闭中,只能通过局部变量才能访问对象。
—栈封闭也被称为线程内部使用或者线程局部使用。
—栈封闭比Ad-hoc封闭更易于维护,也更加健壮。
—对于基本类型的局部变量,无论如何都不会破坏栈封闭性。
—在维持对象引用的栈封闭性时,程序员需要多做一些工作以确保被引用的对象不会逸出。
(3)ThreadLocal类
—ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
—ThreadLocal对象通常用于防止对可变的的单实例变量或全局变量进行共享。
—当某个频繁执行的操作需要一个临时对象,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用该技术。
—当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue来获取初始值。
—ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性。

4 不变性
—如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。
—不可变对象只有一种状态,并且该状态由构造函数来控制。
—不可变对象一定是线程安全的。
—但满足以下条件时,对象才是不可变的:
· 对象创建以后其状态就不能修改;
· 对象的所有域都是final类型;
· 对象是正确创建的(在对象的的创建期间,this引用没有逸出)。
—在不可变对象的内部仍可以使用可变对象来管理它们的状态,如下例:

5 安全发布
(1)不可变对象和初始化安全性
—即使某个对象的引用对其他线程是可见的,也不意味着对象状态对于使用该对象的线程来说一定是可见的。为了确保对象状态能呈现出一致的视图,就必须使用同步。
—Java内存模型为不可变对象的共享提供了一种特殊的初始化安全性保证。
—任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。
(2)安全发布的常用模式
—要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:
· 在静态初始化函数中初始化一个对象引用。例如

public static Holder holder = new Holder(42);
· 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
· 将对象的引用保存到某个正确构造对象的final类型域中。
· 将对象的引用保存到一个由锁保护的域中。如线程安全容器就是通过这种方式。
(3)事实不可变对象
—如果对象从技术上来看是可变的,但其状态在发布后不再改变,那么把这种对象称为事实不可变对象。
—在没有额外的同步的情况下。任何线程都可以安全地使用被安全发布的事实不可变对象。
(4)可变对象
—要安全地共享可变对象,这些对象就必须被安全地发布,并且必须是线程安全的或者由某个锁保护起来。
—对象的发布需求取决于它的可变性:
· 不可变对象可以通过任意机制来发布。
· 事实不可变对象必须通过安全方式来发布。
· 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。
(5)安全地共享对象
—在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
· 线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
· 只读共享。在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
· 线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
· 保护对象。被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。