java 原子类能做什么_Java基础-并发编程-原子类的使用与原理解析

原子类概述

在JDK1.5版本之前,多行代码的原子性主要通过synchronized关键字进行保证。

在JDK1.5版本,Java提供了原子类专门确保变量操作的原子性。

原子类是java.util.concurrent.atomic开发包下的类:

10e7ff01d1de

原子类的原理

原子类的原理:原子类是通过自旋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 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 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 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 atomicStampedReference = new AtomicStampedReference("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问题时不执行更新操作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值