发布:对象能在当前作用域之外的代码中使用。
发布方式:
1、对象引用保存到其他地方
2、public方法返回对象引用
3、引用传递到其他方法
某个不该发布的对象发布,这种情况成为逸出
封装使得程序的正确性进行分析变得可能,这也是封装最重要的意图,
安全的对象构造
构造对象的过程中不要让this引用逸出,常见的是在构造函数中启动一个线程。启动线程后,新的线程能够访问该对象的this,新的线程可能会操作一个尚未构造完成的对象,
线程封闭
将对象的访问限制在单个线程中,如此就不存在线程共享的问题,这种方式位线程封闭
线程封闭是实现线程安全最简单的方式,也是使用较多较好的方式,过多的共享对象会使得系统难以维护。
Ad-hoc线程封闭
维护线程封闭性完全有程序员承担
栈封闭
线程封闭的特例,只有通过局部变量才能访问对象,
局部变量的固有属性就是封闭在直线的线程中,他们位于执行线程的栈中,而栈是线程独占的。
ThreadLocal类
能将值与对象关联,ThreadLocal通常用于防止对可变的单实例对象或全局变量共享
某个频繁执行的操作需要一个临时对象,避免每次执行都重新分配该对象,可以使用ThreadLocal
不变性
不变对象一定是线程安全的
满足不可变对象条件:
1、对象创建后状态不能修改
2、所有域都是final
3、对象是正确创建的,this没有逸出
安全发布
不正确的发布:
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.");
}
}
根据之前的可见性问题,其他线程看到的Holder对象将处于不一致的状态 。
由于没有使用同步来确保Holder对象对其他线程可见,因此Holder被称为未被正确发布-
为正确发布的对象有两个问题:
1、除了发布对象的线程外,其他线程可以看到Holder是一个失效值。
不可变对象
可在不需要额外同步情况下安全发布,
状态不可修改
所有域都是final类型
正确构造
安全发布常用模式
可变对象必须通过安全的方式发布,这意味着发布和使用该对象的线程都必须使用同步。
要安全发布一个可变对象,对象的引用及对象的状态必须同时对其他线程可见。一个正确的构造对象可通过下面几个方式发布,
1、在静态初始化函数中初始化一个对象引用
2、将对象的引用保存到volatile类型的域或者AtomicReferance对象中
3、将对象的引用保存到某个正确构造对象的final类型域中
4、将对象的引用保存到一个由锁保护的域中
发布一个竞态构造对象,最简单安全的方式就是使用静态的初始化器。
public staitc Holder holder = new Holder();
静态初始化器有JVM在类的初始化阶段执行。在jvm内部存在同步机制,这种方式是可以安全发布的
将对象的引用保存到一个由锁保护的域中这条,我们可以安全的发布ArrayList,HashMap这些不安全的对象。
concurrency库容器类提供了多种安全发布保证:
1、将键值对放入Hashtable(不怎么用了)、synchronizedMap或者concurrentMap中
2、某个元素放入vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中
3、BlockQueue、ConcurrentLinkedQueue中,
Future、Exchanger也可以安全发布
事实不可变对象
对象在安全发布后不会修改,没有同步的情况下,安全发布是足够的,当对象引用对所有访问该对象的线程可见,对象发布时的状态对于所有线程也是可见的,并且如果对象状态不会改变,足以保证安全访问。
事实不可见对象,不仅可以简化开发,还可以减少同步提高性能。
可变对象
对象在构造后会发生修改,那么安全发布只是确保发布当时状态的可见性,对于可变对象不仅发布对象时使用同步,而且在每次对象访问时同样使用同步确保后续操作的可见性。
对象的发布取决于其可变性:
不可变对象可以任意安全发布
事实不可变对象必须通过安全机制发布。
可变对象不仅要通过安全方式发布,而且必须是线程安全的或由锁保护的。