jdk版本: 1.8
更多数据结构,算法,设计模式,源码分析等请关注我的微信公众号[技术寨],每周至少两篇优质文章
1.概念
juc的atomic是jdk1.5引入的,完整的包名是:java.util.concurrent.atomic,通过这些类可以帮助我们完成一些原子性的操作。不过它没有像synchronizd那样保证一段代码的原子性,它只能保证一个变量的原子性操作。synchronizd也能保证一个变量的原子性,为什么不用它呢?当然是性能问题,通过使用atomic包下的类来替代synchronizd,可以保证更好的性能。
口说无凭,我们来测试一下:
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class AtomicTest {
private int i = 0;
private AtomicInteger atomicInteger = new AtomicInteger();
@Benchmark
public void synchronizedTest() {
synchronized (Object.class) {
i++;
}
}
@Benchmark
public void atomicTest() {
atomicInteger.addAndGet(1);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(AtomicTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
结果是:
Benchmark Mode Cnt Score Error Units
AtomicTest.atomicTest avgt 5 70.215 ± 1.774 ns/op
AtomicTest.synchronizedTest avgt 5 311.817 ± 28.141 ns/op
我们将线程数调到40试试
Benchmark Mode Cnt Score Error Units
AtomicTest.atomicTest avgt 5 685.087 ± 35.610 ns/op
AtomicTest.synchronizedTest avgt 5 3057.262 ± 219.698 ns/op
结论: atomic类性能是synchronizd的4倍左右,所以如果要控制单个类的原子性修改记得要选择atomic类。
2. 原子类介绍
| 类名 | 引入版本 | 原子性操作变量类型 | 说明 |
|---|---|---|---|
| AtomicInteger | 1.5 | int | 保证整型变量原子性加减等操作 |
| AtomicBoolean | 1.5 | boolean | 保证布尔变量原子性加减等操作,不过内部实现还是通过一个整型变量,1为ture,0为false |
| AtomicIntegerArray | 1.5 | int[] | 保证整型数组变量原子性加减等操作 |
| AtomicIntegerFieldUpdater | 1.5 | int | 它和AtomicInteger作用是一样的,但是AtomicInteger是直接作用在它本身,而AtomicIntegerFieldUpdater可以指定类变量 |
| AtomicLong | 1.5 | long | 参考AtomicInteger |
| AtomicLongArray | 1.5 | long[] | 参考AtomicIntegerArray |
| AtomicLongFieldUpdater | 1.5 | long | 参考AtomicIntegerFieldUpdater |
| AtomicReference | 1.5 | 任何引用类型 | 保证引用类型变量原子性更新等操作 |
| AtomicReferenceArray | 1.5 | 任何引用类型数组 | 保证引用类型数组变量原子性更新等操作 |
| AtomicReferenceFieldUpdater | 1.5 | 任何引用类型 | 参考AtomicIntegerArray |
| AtomicStampedReference | 1.5 | 任何引用类型 | 功能和AtomicReference一样,但是通过时间戳标记更新,解决了CAS的ABA问题 |
| AtomicMarkableReference | 1.5 | 任何引用类型 | 功能和AtomicReference一样,但是通过布尔类型标记是否更新,解决了CAS的ABA问题,它比AtomicStampedReference更轻一点 |
| LongAdder | 1.8 | long | 和AtomicLong功能相似,但是解决了cas在高并发下循环自旋的问题,不过代价就是结果不实时 |
| LongAccumulator | 1.8 | long | LongAdder是它的一种特例,它的功能更强大 |
| DoubleAdder | 1.8 | double | 参考LongAdder |
| LongAccumulator | 1.8 | double | 参考LongAccumulator |
3.相关知识简介
3.1 CAS
if(v == a){
v = b;
}
我们知道按照上面代码的写法,一定会存在并发问题。但是CPU帮我提供了一种底层方式保证这种操作的原子性,这就是CAS(Compare and swap)。它通过对比某一个内存地址的值之后再更新,每次只能有一个线程操作成功,从而保证其原子性。
3.2 volatile变量
在介绍volatile变量之前一定要介绍并发编程中三个非常重要的概念。
- 原子性:一个操作要么成功要么失败
- 可见性:一个线程修改之后其他线程能马上看到
- 有序性:按照代码顺序执行
volatile能保证的只是上面的两个特性:可见性和有序性。原子性并不能保证,比如:
volatile int v = 0;
public void volatileTest(){
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for(int j = 0 ; j < 1000; j++){
v++;
}
}).start();
}
}
如果你执行上面的代码会发现,极大可能v的最终结果不是100000,原因就是volatile不能保证原子性。
可是,通过前面cas介绍,我们知道它可以保证原子性,而volatile又能保证可见性和有序性,所以它们就’双剑合璧’了,产生了我们看到的atomic类。
说明:这里只是简单介绍CAS和volatile变量,给大家一个直观的感受,后面会详细介绍jvm内存模型,CAS存在的问题等内容。
关注我的公众号,不迷路

1984

被折叠的 条评论
为什么被折叠?



