注:上面例子说的说可见性问题,其实归根结底还是多线程并发执行的时候,不同线程推进速度无法预测的问题!输出0的原因是可能发生了重排序(在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整)。
3.1.1 失效数据
注意上述3-3代码和上章中的Vector复合操作(不满足原子性线程不安全)的区分。3-3中代码,只要执行set/get方法就能拿到对象的锁,自然保证原子性了啊。。
3.1.2 非原子的64位操作
3.1.3 加锁与可见性
内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果,如图3-1所示。
加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。
3.1.4 Volatile变量
本节参考资料
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量的操作与其他内存操作一起重排序(插入内存屏障)。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此读取volatile类型的变量时总会返回最新写入的值。
class VolatileFeaturesExample {
volatile long vl = 0L; //使用volatile声明64位的long型变量
public void set(long l) {
vl = l; //单个volatile变量的写
}
public void getAndIncrement () {
vl++; //复合(多个)volatile变量的读/写
}
public long get() {
return vl; //单个volatile变量的读
}
}
上面代码与下面代码语意相同!
class VolatileFeaturesExample {
long vl = 0L; // 64位的long型普通变量
public synchronized void set(long l) { //对单个的普通 变量的写用同一个监视器同步
vl = l;
}
public void getAndIncrement () { //普通方法调用
long temp = get(); //调用已同步的读方法
temp += 1L; //普通写操作
set(temp); //调用已同步的写方法
}
public synchronized long get() {
//对单个的普通变量的读用同一个监视器同步
return vl;
}
}
- 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
- 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
3.2 发布与逸出
“发布(Publish)”一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。
上述代码中的states本是私有变量,但getStates方法把这个变量发布到对象外部也能直接修改了,这就逸出了。。
3.3 线程封闭
3.3.1 Ad-hoc线程封闭
3.3.2 栈封闭
局部变量的固有属性之一就是封闭在执行线程中。在维护对象引用的栈封闭性时,程序员需要多做一些工作以确保被引用对象不会逸出。
3.3.3 ThreadLocal类
3.4 不变性
不可变对象一定是线程安全的。
3.5.1 不正确的发布
3.5.2 不可变对象与初始化安全性
任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。