作者: 康凯森
日期: 2016-11-19
分类: Java
本文将介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问。
为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
加锁的含义不仅有互斥行为,还包括内存可见性。
在没有同步的情况下,编译器
、处理器
、运行时
等都可能对操作的执行顺序
进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论。
在多线程程序中使用共享且可变
的long和double
等类型的变量也是不安全的,除非用关键字volatile
来声明它们,或者用锁保护起来。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
volatile变量只能确保内存可见性。
volatile变量的一种典型用法:检查某个状态标记以判断是否退出循环。
对象的发布:使对象能够在当前作用域之外的代码中使用。
对象的逸出:某个不应该发布的对象被发布。
当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。
/**
* bad case: 使内部的可变状态逸出
* Created by kangkaisen on 2016/11/19.
*/
public class UnsafeStates {
private String[] states = new String[] { "KKS", "test" };
public String[] getStates() {
return states;
}
}
/**
* bad case: 隐式地使this引用逸出
* Created by kangkaisen on 2016/11/19.
*/
public class ThisEscape {
public ThisEscape(EventSource source) {
source.addErrorListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
}
);
}
}
如果需要在构造函数中注册一个事件监听器或启动线程,那么可以使用一个私有的构造函数和一个公共的工厂方法。
线程封闭:仅在单线程内访问数据。
不可变对象:一个对象在创建后其状态就不能被修改,则称此对象是不可变对象。
不可变对象一定是线程安全的。
不可变对象需要满足的条件:
final域能够确保初始化过程中的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步。
除非某个域是可变的,否则应将其声明为final域。
每当需要对一组相关数据以原子方式执行某个操作时,就可以考虑创建一个不可变类来包含这些数据。也就是说,对于在访问和更新多个相关变量时出现的竞争条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。
一个正确构造的对象可以通过以下方式来安全地发布:
对象的发布需求取决于它的可变性:
Date
对象)必须通过安全方式来发布。在并发程序中使用和共享对象时,可以使用的一些实用策略:
本文是《Java 并发编程实战》的读书笔记。