原子类概述
在JDK1.5版本之前,多行代码的原子性主要通过synchronized关键字进行保证。
在JDK1.5版本,Java提供了原子类专门确保变量操作的原子性。
原子类是java.util.concurrent.atomic开发包下的类:
原子类的原理
原子类的原理:原子类是通过自旋CAS操作volatile变量实现的。
- CAS是compare and swap的缩写,即比较后(比较内存中的旧值与预期值)交换(将旧值替换成预期值)。
- atomic类会多次尝试CAS操作直至成功或失败,这个过程叫做自旋。
- CAS是sun.misc包下Unsafe类提供的功能,需要底层硬件指令集的支撑,底层硬件指令集的调用通过JNI调用(native方法)。
- 使用volatile变量是为了多个线程间变量的值能及时同步。
原子类分类
分组 | 功能描述 | 原子变量类 |
---|---|---|
基础数据型 | 提供对boolean、int、long的原子性操作 | AtomicBoolean AtomicInteger AtomicLong |
数组型 | 提供对数组元素的原子性操作 | AtomicLongArray AtomicIntegerArray AtomicReferenceArray |
字段更新器 | 提供对指定对象的指定字段进行原子性操作 | AtomicLongFieldUpdater AtomicIntegerFieldUpdater AtomicReferenceFieldUpdater |
引用型 | 提供对对象的原子性操作 | AtomicReference AtomicStampedReference AtomicMarkableReference |
原子累加器(jdk1.8后) | AtomicLong和AtomicDouble的升级类型,专门用于数据统计,性能更高 | DoubleAdder LongAdder |
基础数据型使用实例
Atomic类常用方法 ,以AtomicInteger为例
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
常见方法代码测试:
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAtomicInteger {
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()-> {
for (int i1 = 0; i1 < 10000; i1++) {
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.intValue());//顺序打印到100000
}
}).start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()-> {
for (int i1 = 0; i1 < 10000; i1++) {
atomicInteger.getAndDecrement();
System.out.println(atomicInteger.intValue());//顺序打印到-100000
}
}).start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()-> {
for (int i1 = 0; i1 < 1000; i1++) {
atomicInteger.getAndAdd(2);
System.out.println(atomicInteger.intValue());//顺序打印从2到20000的所有偶数
}
}).start();
}
}
}
使用场景
基础数据型适合并发情况下对int,long,boolean类型变量的使用++、--
等多指令操作
常见功能实现:使用AtomicLong实现一个计时器
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicLong;
/**
* 基于AtomicLong的计时器
*/
public class Indicator {
private static final Indicator INDICATOR = new Indicator();
private Indicator() {}
public static Indicator getInstance() {
return INDICATOR;
}
private final AtomicLong requestCount = new AtomicLong();
public void newRequestReceive() {
requestCount.getAndIncrement();
}
public long getRequestCount() {
return requestCount.get();
}
}
测试代码:
package com.study.thread.atomic;
public class TesIndicator {
public static void main(String[] args) {
Indicator indicator = Indicator.getInstance();
new Thread(()->{
for (int i = 0; i < 10000; i++) {
indicator.newRequestReceive();
}
}).start();
new Thread(()->{
for (int i = 0; i < 20000; i++) {
indicator.newRequestReceive();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(indicator.getRequestCount());
}
}
// 30000
比AtomicLong更高效的DoubleAdder
//1、设置BenchmarkMode为Mode.Throughput,测试吞吐量
//2、设置BenchmarkMode为Mode.AverageTime,测试平均耗时
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.Throughput)
public class Main {
private static AtomicLong count = new AtomicLong();
private static LongAdder longAdder = new LongAdder();
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder().include(Main.class.getName()).forks(1).build();
new Runner(options).run();
}
@Benchmark
@Threads(10)
public void run0(){
count.getAndIncrement();
}
@Benchmark
@Threads(10)
public void run1(){
longAdder.increment();
}
}
下面通过JMH比较一下AtomicLong 和 LongAdder的性能。
线程数为1
1、吞吐量
Benchmark Mode Cnt Score Error Units
Main.run0 thrpt 5 154.525 ± 9.767 ops/us
Main.run1 thrpt 5 89.599 ± 7.951 ops/us
2、平均耗时
Benchmark Mode Cnt Score Error Units
Main.run0 avgt 5 0.007 ± 0.001 us/op
Main.run1 avgt 5 0.011 ± 0.001 us/op
单线程情况:AtomicLong的吞吐量和平均耗时都占优势
线程数为10
1、吞吐量
Benchmark Mode Cnt Score Error Units
Main.run0 thrpt 5 37.780 ± 1.891 ops/us
Main.run1 thrpt 5 464.927 ± 143.207 ops/us
2、平均耗时
Benchmark Mode Cnt Score Error Units
Main.run0 avgt 5 0.290 ± 0.038 us/op
Main.run1 avgt 5 0.021 ± 0.001 us/op
并发线程为10个时:
- LongAdder的吞吐量比较大,是AtomicLong的10倍多。
- LongAdder的平均耗时是AtomicLong的十分之一。
一些高并发的场景,比如限流计数器,建议使用LongAdder替换AtomicLong。
数组型使用实例
核心方法:
- get(int i):获取数组第i个下标的元素
- getAndIncrement(int i):将第i个下标元素加一
- getAdnAdd(int i,int delta):将第i个下标的元素增加delta(可为负数)
- compareAndSet(int i,int expect,int update):进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
测试方法代码:
package com.study.thread.atomic;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class IntegerArrayTest {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
private static int[] integerArray = new int[10];
public static void main(String[] args) {
System.out.println(atomicIntegerArray);
AddThread[] arr = new AddThread[500];//线程数越越明显
for (int i = 0; i < 500; i++) {
arr[i] = new AddThread();
}
for (int i = 0; i < 500; i++) {
arr[i].start();
}
//将线程加入主线程中,执行完再执行主线程
for (int i = 0; i < 500; i++) {
try {
arr[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(atomicIntegerArray);
System.out.println(Arrays.toString(integerArray));
}
static class AddThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
for (int i1 = 0; i1 < 1000; i1++) {
atomicIntegerArray.getAndIncrement(i);//将第i个下标元素加一
integerArray[i]++;//使用多指令操作元素加一
}
}
}
}
}
//打印结果
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[500000, 500000, 500000, 500000, 500000, 500000, 500000, 500000, 500000, 500000]
[499729, 499561, 499352, 499122, 499149, 499156, 499176, 499004, 498873, 499131]
字段更新器使用实例
字段更新器注意事项:
- 初始化字段更新器使用AtomicIntegerFieldUpdater.newUpdater
- 不支持static字段
- 必须使用volatile
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
private static AtomicInteger atomicInteger = new AtomicInteger();
private static AtomicIntegerFieldUpdater<FieldDTO> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(FieldDTO.class, "score");
public static void main(String[] args) throws InterruptedException {
FieldDTO fieldDTO = new FieldDTO();
Thread[] arrThread = new Thread[1000];
for (int i = 0; i < 1000; i++) {
arrThread[i] = new Thread(()->{
atomicInteger.getAndIncrement();
atomicIntegerFieldUpdater.incrementAndGet(fieldDTO);
});
}
for (int i = 0; i < 1000; i++) {
arrThread[i].start();
}
for (int i = 0; i < 1000; i++) {
arrThread[i].join();
}
System.out.println(fieldDTO.score);
System.out.println(atomicInteger.get());
}
static class FieldDTO{
int id ;
String name;
volatile int score;
}
}
//打印结果
1000
1000
引用型使用类型使用实例
AtomicReference使用示例
常用方法:compareAndSet
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
static AtomicReference<String> atomicReference = new AtomicReference<>("123");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
new Thread(()->{
if (atomicReference.compareAndSet("123", "456")) {
System.out.println(Thread.currentThread().getName().concat("字符串由123改成了456."));
}
},"修改属性"+i).start();
}
for (int i = 0; i < 100; i++) {
new Thread(()->{
if (atomicReference.compareAndSet("456", "123")) {
System.out.println(Thread.currentThread().getName().concat("字符串由456改成了123!"));
}
},"还原属性"+i).start();
}
Thread.sleep(2000);
System.out.println(atomicReference.get());
}
}
//打印结果 每次可能不一样 四个线程执行了操作
修改属性0字符串由123改成了456.
还原属性0字符串由456改成了123!
修改属性9字符串由123改成了456.
还原属性72字符串由456改成了123!
123
AtomicReference中的ABA问题
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
static AtomicReference<String> atomicReference = new AtomicReference<>("123");
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(()->{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicReference.compareAndSet("123", "456")) {
System.out.println(Thread.currentThread().getName().concat("字符串由123改成了456!"));
}
},"修改属性2");
Thread t1 = new Thread(()->{
if (atomicReference.compareAndSet("123", "456")) {
System.out.println(Thread.currentThread().getName().concat("字符串由123改成了456."));
}
System.out.println(Thread.currentThread().getName().concat("---").concat(atomicReference.get()));
if (atomicReference.compareAndSet("456", "123")) {
System.out.println(Thread.currentThread().getName().concat("字符串由456改成了123."));
}
},"修改属性1");
t2.start();
t1.start();
t2.join();
t1.join();
System.out.println(atomicReference.get());//456 执行完之后修改了
}
}
//执行结果 执行完之后修改了
//事实上在修改属性1线程操作字符串后,修改属性2是不想再修改了 发生了ABA
修改属性1字符串由123改成了456.
修改属性1---456
修改属性1字符串由456改成了123.
修改属性2字符串由123改成了456!
456
使用AtomicStampedReference解决CAS中的ABA问题
package com.study.thread.atomic;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicReferenceDemo {
static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<String>("123", 0);
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
int start = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName().
concat("版本号".concat(String.valueOf(atomicStampedReference.getStamp()))));//版本号
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int end = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName().
concat("版本号".concat(String.valueOf(atomicStampedReference.getStamp()))));//版本号
if (atomicStampedReference.compareAndSet("123", "456", start, end)) {
System.out.println(Thread.currentThread().getName().concat("字符串由123改成了456!"));
}
}, "修改属性2");
Thread t1 = new Thread(() -> {
if (atomicStampedReference.compareAndSet("123",
"456", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)) {
System.out.println(Thread.currentThread().getName().concat("字符串由123改成了456."));
}
System.out.println(Thread.currentThread().getName().concat("---")
.concat(atomicStampedReference.getReference()));
if (atomicStampedReference.compareAndSet("456",
"123", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)) {
System.out.println(Thread.currentThread().getName()
.concat("字符串由456改成了123."));
}
}, "修改属性1");
t2.start();
t1.start();
t2.join();
t1.join();
System.out.println(atomicStampedReference.getReference());//123 通过版本号控制,出线ABA问题时可以不更新
}
}
//打印结果 修改属性2线程执行逻辑前后版本好不一致了,那修改操作就没有操作了,解决了ABA问题
修改属性2版本号0
修改属性1字符串由123改成了456.
修改属性1---456
修改属性1字符串由456改成了123.
修改属性2版本号2
123
总结:AtomicStampedReference通过版本号控制,出线ABA问题时不执行更新操作了。
网友评论