什么是线程安全性?
在《Java Concurrency In Practice》中,有这么一段对于线程安全性的描述,
被广泛引用:
当多个线程访问某个类时,不管运行时环境采取
何种调度方式
或者这些线程将如何交替执行,并且在
主调代码中不需要任何额外的同步或者协同
,这个类都能表现出正确的行为
,那么就称这个类是线程安全的
---- P13. Java Concurrency In Practice
而线程安全性主要体现在3个方面:
下面我们使用代码例子来从这3个方面来测试线程安全性
说起原子性,我们不得不介绍java.util.concurrency.atomic
包我们现在对上一节
ConcurrencyTest测试类
代码进行改进,具体改进如下:
public static AtomicInteger count = new AtomicInteger(0);
// ...
log.info("Count: {}", count.get());
// ...
private static void add(){
count.incrementAndGet();
// or count.getAndIncrement();
}
现在我们测试运行,发现每次测试,输出都为Count: 5000
现在我们可以给此类注释为@ThreadSafe
现在我们关键分析一下count.incrementAndGet();
操作。
count.incrementAndGet();
↓
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
↓
// sun.misc.Unsafe.class
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
getIntVolatile
和compareAndSwapInt
都是native底层实现
现在看到getAndAddInt函数:
var1
就是count对象本身var2
“当前值”(工作内存,可能并没有刷写到内存值上,导致不同步)var4
需要增加的值,这里为1var5
从底层获取的"当前值"var2
与底层获取的当前值var5
相等,那么最终就将结果加上一个偏移量var5注意,上面所说的"当前值"
有着特殊的语义,这个需要我们了解Java的内存模型中关于工作内存
,主内存
的概念。
compareAndSwapInt
也就是我们常说的CAS(Compare And Swap)
具体参考Java Concurrency In Practice P262
和 深入理解Java虚拟机 P394
上面的compareAndSwapInt也可以用深入理解Java虚拟机
里面的描述来理解:
- CAS指令需要3个操作数:V,A,B。其中V为底层内存值,A为旧的预期值,B为需要替换的新值。CAS指令执行时,
当且仅当V符合旧的预期值A时,处理器才能用新值B更新V的值。- CAS为原子操作
AtomicLong 与 LongAddr区别,参考文章:
https://www.jianshu.com/p/ec045c38ef0c
AtomicIntegerFieldUpdater。原子更新volatile non-static
字段。
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
@Slf4j
public class AtomicUpdateFiled {
private static AtomicIntegerFieldUpdater<AtomicUpdateFiled> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicUpdateFiled.class, "count");
@Getter
public volatile int count = 100; // 不能用static修饰
private static AtomicUpdateFiled cls = new AtomicUpdateFiled();
public static void main(String[] args) {
if( updater.compareAndSet(cls, 100, 200) ){
log.info("update success 1, {}", cls.getCount());
}
if( updater.compareAndSet(cls, 100, 200) ){
log.info("update success 2, {}", cls.getCount());
} else {
log.info("update fail, {}", cls.getCount());
}
}
}
/*
运行结果如下:
update success 1, 200
update fail, 200
*/
CAS的ABA问题解决方案:AtomicStampedReference
类。
每次对象状态被改变,它的版本号+1。
共享变量在线程间不可见的原因: