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说好的线程安全怎么却不安全了呢?输出结果:
```java
sync result:1000000
sync result cost:151
atomic result:1000001
atomic 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:1000000
sync result cost:196
atomic result:1000000
atomic 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:100000000
sync result cost:6342
atomic result:100000000
atomic 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() {
@Override
public 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。