volatile变量的使用建议
使用volatile变量的主要原因是其单个字段同步操作的简易性。如果只使用了volatile就能实现线程安全,就放心地使用它。否则如果在同时还需要添加其他的同步措施,那就不要使用。
正确使用的场景举例:变量本身标志是一种状态,在代码中需要确保这些状态的可见性,这时就可使用volatile。
volatile变量的使用仅仅是一个状态标志, 用于指示发生了一个重要的一次性事件, 例如完成初始化或请求终止。
volatile变量的使用建议及错误用法
这样只要任何一个线程调用了shutdown() , 其他线程在执行do Work时都可以立即感知到shutdown Requested变量的变化。这种类型的状态标记的一个公共特性是:通常只有一种状态转换,如标志从false转换为true。
这时使用volatile比synchronized要简单得多, 同时使用synchronized还会影响系统的吞吐量。
volatile变量的错误用法
注意:单个volatile变量单独的读、写操作具有原子性。但是对于类似于++, -, 逻辑非!这种复合操作,这些操作整体上不具有原子性。
volatile变量的使用建议及错误用法
如下面例子:
volatile变量的使用建议及错误用法
造成这种情况的原因是因为++操作分三个操作完成的。我们执行反编译命令java p-c
Volatile Test.class, increase() 函数中race++, 我们看到由6条字节码指令构成(return指令不是race++产生的, 不算在内) 。
volatile变量的使用建议及错误用法
字节码释义如下:
a load_o将this引用推送至栈顶
dup复制栈顶值, 并将其压入栈顶, 即此时操作数栈上有连续相同的this引用;
get field弹出栈顶的对象引用, 获取其字段race的值并压入栈顶。第一次操作
iconst _ 1将int型(1) 推送至栈顶
iadd弹出栈顶两个元素相加(race+1) , 并将计算结果压入栈顶。第二次操作
put field从栈顶弹出两个变量(累加值, this引用) , 将值赋值到this实例字段race上。
第三次操作从字节码层面很容易分析出来并发失败的原因了,假如有两条线程同时执行这条语句,
(1) 线程A, 线程B同时执行get field指令把race的值取到操作栈顶时, volatile关键字可以保证来race的值在此时是正确(最新的值)的。
(2) 线程B依次执行完了后续操作i add和put field, 此时主内存中race的值已被增大1。
(3) 线程A操作栈顶的race值race值就变成了过期的数据。这时线程A执行i add、put field后就会把较小的值同步会主内存了。
所以volatile变量只能保证共享变量的可见性, 不能保证复合操作的原子性。在此种场景中, 我们仍然要通过加锁(synchronized或JUC包中的原子类) 来保证原子性。