非原子的64操作
- 非volatile类型的64位数值变量(double和long),JVM允许将64位的读或写操作分解为两个32位操作,因此在多线程中使用共享且可变的long与double类型的变量是不安全的,需要使用volatile声明或者用锁保护起来
volatile变量
- 保证可见性且防止重排序
发布与逸出
发布: 使对象能够在当前作用域之外的代码中使用
- 不安全的发布
/**
* 私有的数组states已经逸出了它所在的作用域,致使调用者可以修改这个数组的内容
*/
class UnsafeStates{
private String[] states = new String[]{"A","B"};
public String[] getStates(){ return states; }
}
- this引用逸出
public class ThisEscape{
public ThisEscape(EventSource source){
source.registerListener(
new EventListener(Event e){
//this对象(ThisEscape)逸出,仅当ThisEscape构造函数返回时this对象才算完整,
//此处发布不完整的this对象(对象可能没有构造完成,就共享给其他线程使用)
doSomething(e);
}
);
}
void doSomething(Event e){}
}
- 使用工厂方法防止this引用在构造过程中逸出
/**
* 使用工厂方法防止this引用在构造过程中逸出
*/
public class SafeListener{
//必须用final修饰
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e){
doSomething(e);
}
};
}
void doSomething(Event e){}
//工厂方法
public static SafeListener newInstance(){
//先创建完整的对象(this)
SafeListener safe = new SafeListener();
//再将(this)发布使用
source.registerListener(safe.listener);
return safe;
}
}
线程封闭
多线程间不共享数据,就不需要同步,称为线程封闭
Ad-hoc线程封闭
完全由实现着控制的线程封闭,非常脆弱,没有任何一种语言特性能将对象封闭到目标线程上
栈封闭
局部变量就为栈封闭,局部变量是不被多个线程共享的,局部变量封闭在执行线程中,位于执行线程的栈中,其他线程无法访问,优于Ad-hoc封闭
ThreadLocal类
使用ThreadLocal类可以实现线程封闭,ThredLocal使得各个线程都能够保持各自独立的一个对象,每个线程都会重新创建一个对象与线程绑定(以空间换时间),而线程是否安全取决于初始化方法中你如何创建该对象.(使用ThreadLocal可以达到线程内全局变量效果)
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
//重写初始化方法(为每个线程创建单独对象进行绑定时调用,
//是否线程安全(达到线程封闭效果)取决于初始化方法如何创建对象)
@Override
protected Connection initialValue() {
//返回一个新的数据库链接(Connection)对象
return DriverManager.getConnection(DB_URL);
}
};
//获取连接对象,在多线程中使用同一个对象调用该方法,
//则会每个线程获取一个独立的Connection对象与线程绑定(K,V)形式绑定
public static Connection getConnection(){
return connectionHolder.get();
}
不可变
- 不可变对象一定是线程安全的
- 对象创建后其状态就不能修改
- 对象的所有域是final类型
- 对象是正确创建的(创建期间,this引用没有逸出)
安全发布
- 不安全的发布
//不安全的发布(由于可能指令重排序,不能保证发布时对象的完整性)
//1.可使用final修饰解决,参考final内存语义
//2.或者保证Holder类不可变,也可以安全发布
public Holder holder;
public void initialize(){
//创建对象时,可能存在指令重排序
holder = new Holder(42);
}
public class Holder {
//问题并不在本类,但是解决不安全发布导致的问题的另一种方式就是该类对象不可变,
//因此使用final修饰n即可,(参考final内存语义)
private int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
- 安全发布的常用模式
- 在静态初始化函数中初始化一个对象引用(JVM在类初始化阶段创建,JVM内部存在着同步机制,由jvm保证安全发布)
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中(可见性,原子性)
- 将对象引用保存到正确构造对象的final类型域中(参考final内存语义)
- 对象的引用保存到一个由锁保护的域中(同步处理,如同步容器)
- 并发程序中使用共享对象,可用的一些策略
- 线程封闭: 对象被封闭在线程内,只能由这个线程修改
- 只读共享: 共享的只读对象包含不可变对象,和事实不可变对象
- 线程安全共享: 线程安全的对象在其内部实现同步
- 保护对象: 被保护的对象只能通过持有特定的锁来访问