atomic原子类
atomic包中是一些基于CAS实现的工具类,前面已经介绍过CAS并且演示了AtomicStampedReference的使用,这一部分主要演示一下其他工具类的使用,就不过多的进行分析。
AtomicBoolean
AtomicBoolean提供了原子的更新某些标志位的功能,由JDK1.5引入,并不能用作boolean的替代品。AtomicBoolean的适用场景不多,适合一些并发初始化且只需要初始化一次资源的场景,也可以用它来优雅的初始化资源。
package atomic;import java.util.concurrent.atomic.AtomicBoolean;/*** BooleanTest** @author starsray* @date 2021/12/21*/public class BooleanTest {/*** 初始化*/private static AtomicBoolean initialized = new AtomicBoolean(false);/*** 初始化*/public void init() {if (initialized.compareAndSet(false, true)) {//here is the initialization code}}}
AtomicInteger
AtomicInteger是原子的对int类型的数字进行加减操作,由JDK1.5引入,相对于添加synchronized块的计数功能,基于CAS实现的AtomicInteger要更加高效率一点。通过一个示例来演示AtomicInteger的使用,开启2个线程测试从0增加到1000_000消耗的时间,并打印预期结果:
- testSync使用synchronized
- testAtomic使用AtomicInteger ```java package atomic;
import java.time.Duration; import java.time.Instant; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger;
/**
- 整数测试 *
- @author starsray
@date 2021/12/21 / public class IntegerTest { /*
阈值 */ private static final int threshold = 1000_000;
/**
- 初始化 / private static int init = 0; /*
最初的 */ private static final AtomicInteger initial = new AtomicInteger(0);
/**
- 线程数 / private static final int threadCount = 2; /*
锁 */ static final Object lock = new Object();
/**
- 主要 *
- @param args arg游戏
@throws InterruptedException 中断异常 */ public static void main(String[] args) throws InterruptedException { testSync(); testAtomic(); }
/**
- 测试同步 *
@throws InterruptedException 中断异常 */ static void testSync() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Instant start = Instant.now(); for (int i = 0; i < threadCount; i++) {
new Thread(() -> {while (true) {synchronized (lock) {if (init < threshold) {init++;} else {countDownLatch.countDown();break;}}}}).start();
} countDownLatch.await(); Instant end = Instant.now(); System.out.printf(“sync result:%s%n”, init); System.out.printf(“sync result cost:%s%n”, Duration.between(start, end).toMillis()); }
/**
- 测试原子 *
- @throws InterruptedException 中断异常
*/
static void testAtomic() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
Instant start = Instant.now();
for (int i = 0; i < threadCount; i++) {
} countDownLatch.await(); Instant end = Instant.now(); System.out.printf(“atomic result:%s%n”, initial.intValue()); System.out.printf(“atomic result cost:%s%n”, Duration.between(start, end).toMillis()); } }new Thread(() -> {while (true) {if (initial.intValue() < threshold){initial.incrementAndGet();}else{countDownLatch.countDown();break;}}}).start();
查看最终的执行时间,AtomicInteger不仅算的快,而且还多操作了一次递增,使用synchronized操作的结果是符合预期的,虽然慢了点,但是准确性更重要。AtomicInteger说好的线程安全怎么却不安全了呢?输出结果:```javasync result:1000000sync result cost:151atomic result:1000001atomic result cost:13
其实并不是AtomicInteger不安全了,而是在使用的时候需要注意的一个点,在Java中i++并不是一个原子的操作,使用synchronized保证了线程独占,CPU在某个时间片只有一个线程可以对变量进行操作,即便不是原子操作也不会影响到计算结果,但是AtomicInteger只能保证把++包装成一个原子操作,并不能保证线程的独占,而这个实验中的计算结果1000001也说明了在最后一次递增前也就是threshold-1时,多个线程同时进入,在进入下一次循环时break才跳出循环。
出现问题的代码就在这一段:
if (initial.intValue() < threshold){initial.incrementAndGet();}else{countDownLatch.countDown();break;}
讲代码进行调整,重新验证
synchronized (lock){if (initial.intValue() < threshold){initial.incrementAndGet();}else{countDownLatch.countDown();break;}}
输出结果:
sync result:1000000sync result cost:196atomic result:1000000atomic result cost:144
由于处理逻辑都是用到了synchronized所以效率差不多,按理说i++应该比incrementAndGet更快,这里也反映了AtomicInteger的效率是很高的。
上面的例子说明了在使用AtomicInteger时候要考虑场景,关于计数常见有下面的场景示例:
场景一 :——————————————————————————>| 已知起点、终点 场景二 :|——————————————————————————> 已知起点 场景三: |<—————————————————————————-| 已知终点 —> 0
场景三算是场景一的逆向特例,对于场景二更适合使用AtomicInteger原子类,因为第一种场景使用不能保证临界点线程独占,因此可能会有多个线程进入操作,从而导致结果的不准确。
package atomic;import java.time.Duration;import java.time.Instant;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;/*** 智力测试** @author starsray* @date 2021/12/22*/public class IntTest {/*** 初始化*/private static int init = 0;/*** 最初的*/private static final AtomicInteger initial = new AtomicInteger(0);/*** 线程数*/private static final int threadCount = 100;/*** 阈值*/private static final int threshold = 1000_000;/*** 锁*/private static final Object lock = new Object();/*** 主要** @param args arg游戏* @throws InterruptedException 中断异常*/public static void main(String[] args) throws InterruptedException {testSync();testAtomic();}/*** 测试同步** @throws InterruptedException 中断异常*/static void testSync() throws InterruptedException {Instant start = Instant.now();CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {int j = 0;while (j < threshold) {j++;synchronized (lock) {init++;}}countDownLatch.countDown();}).start();}countDownLatch.await();Instant end = Instant.now();System.out.printf("sync result:%s%n", init);System.out.printf("sync result cost:%s%n", Duration.between(start, end).toMillis());}/*** 测试原子** @throws InterruptedException 中断异常*/static void testAtomic() throws InterruptedException {Instant start = Instant.now();CountDownLatch countDownLatch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {int j = 0;while (j < threshold) {j++;initial.incrementAndGet();}countDownLatch.countDown();}).start();}countDownLatch.await();Instant end = Instant.now();System.out.printf("atomic result:%s%n", init);System.out.printf("atomic result cost:%s%n", Duration.between(start, end).toMillis());}}
输出结果:
sync result:100000000sync result cost:6342atomic result:100000000atomic result cost:1868
明显的展示了基于CAS的AtomicInteger的速度,也保证了准确性。
AtomicLong/LongAccumulator/LongAdder
- AtomicLong
由JDK1.5引入,使用方法同AtomicInteger,区别类似于Long与Integer,在表示数据长度上的区别。
- LongAccumulator
JDK1.8引入,该类继承自Striped64类,查看该类都继承关系,及内部的属性方法:
- Striped64类是JDK1.8引入。
- Cell类是Striped64的内部类。
并发效率高的特性。
- LongAdder
LongAdder是LongAccumulator的一个特例,LongAdder 类为维护计数和总和的常见特殊情况提供了此类功能的类似物。调用 new LongAdder() 等价于 如下代码:
// LongAdder使用LongAdder longAdder = new LongAdder();// LongAccumulator使用LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {@Overridepublic long applyAsLong(long left, long right) {return left + right;}}, 0);
使用Java新特性Lambda表达式及对象引用可以简写为:
// LongAdder使用LongAdder longAdder = new LongAdder();// LongAccumulator使用LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);
对于简单的递增递减可以使用LongAdder来进行操作,如果对于累加初始值非0,或者相对复杂的累加运算规则使用LongAccumulator。
