【JUC】原子操作类
1. 原子操作类
原子操作类如下所示:
- AtomicBoolean
- AtomicInteger
- AtomicIntegerArray
- AtomicIntegerFieldUpdater
- AtomicLong
- AtomicLongArray
- AtomicLongFieldUpdater
- AtomicMarkableReference
- AtomicReference
- AtomicReferenceArray
- AtomicReferenceFieldUpdater
- AtomicStampedReference
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
根据每个类的特点可以将其分成几种不同的类型:
1.1 基本类型原子类
基本类型原子类包含以下三类:
- AtomicInteger
- AtomicBoolean
- AtomicLong
以 AtomicInteger
为例,其常用API如下(其他类也差不多):
- 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)
以 AtomicInteger
演示 i++
:
class MyNumber {
AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus() {
atomicInteger.getAndIncrement();
}
}
public class AtomicIntegerDemo {
public static final int SIZE = 50;
public static void main(String[] args) throws InterruptedException {
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 1; i <= SIZE; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
myNumber.addPlusPlus();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
//等待上面50个线程全部计算完成后,再去获得最终值
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t" + "result: " + myNumber.atomicInteger.get());
}
}
运行结果如下:
1.2 数组类型原子类
数组类型原子类包含以下三类:
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
以 AtomicIntegerArray
演示基本用法:
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.println(atomicIntegerArray.get(i));
}
System.out.println();
int tmpInt = 0;
tmpInt = atomicIntegerArray.getAndSet(0, 1122);
System.out.println(tmpInt + "\t" + atomicIntegerArray.get(0));
tmpInt = atomicIntegerArray.getAndIncrement(0);
System.out.println(tmpInt + "\t" + atomicIntegerArray.get(0));
}
}
运行结果如下所示:
1.3 引用类型原子类
引用类型原子类包括以下三类:
- AtomicReference
- AtomicStampedReference
- 携带版本号的引用类型原子类,可以解决
ABA问题
- 解决修改过几次
- 状态戳原子引用
- 携带版本号的引用类型原子类,可以解决
- AtomicMarkableReference
- 原子更新带有标记位的引用类型对象
- 解决是否修改过(将状态戳简化为true/false)
- 状态戳(true/false)原子引用
1.3.1 AtomicReference
AtomicReference
使用实例:
class User {
String userName;
int age;
}
/**
* @auther zzyy
* @create 2022-02-24 14:50
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("z3", 22);
User li4 = new User("li4", 28);
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
}
}
运行结果如下:
使用 AtomicReference
实现一个自旋锁:
需求:
- 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟
- B随后进来后发现,当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t" + "----come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void unLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t" + "----task over,unLock...");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
}, "A").start();
//暂停500毫秒,线程A先于B启动
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
}, "B").start();
}
}
1.3.2 AtomicStampedReference
携带版本号的引用类型原子类,可以解决 ABA问题
。可以记录修改过几次变量。
示例如下:
public class ABADemo {
static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
//暂停500毫秒,保证后面的t4线程初始化拿到的版本号和我一样
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t" + "2次流水号:" + stampedReference.getStamp());
stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t" + "3次流水号:" + stampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
//暂停1秒钟线程,等待上面的t3线程,发生了ABA问题
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
}, "t4").start();
}
}
运行结果如下:
1.3.3 AtomicMarkableReference
原子更新带有标记位的引用类型对象,它的定义就是将状态戳简化为 true/false
。
示例代码:
public class AtomicMarkableReferenceDemo {
static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);
public static void main(String[] args) {
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
//暂停1秒钟线程,等待后面的T2线程和我拿到一样的模式flag标识,都是false
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100, 1000, marked, !marked);
}, "t1").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASresult: " + b);
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());
}, "t2").start();
}
}
运行结果:
1.4 对象的属性修改原子类
对象的属性修改原子类包含以下三种:
- AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值
- AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
- AtomicReferenceFieldUpdater:原子更新引用类型字段的值
**使用目的:**以一种线程安全的方式操作非线程安全对象内的某些字段
使用要求:
- 更新的对象属性必须使用
public volatile
修饰符。 - 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性。
1.4.1 AtomicIntegerFieldUpdater
使用示例:
1)需求:
- 以一种线程安全的方式操作非线程安全对象的某些字段。
- 10个线程,每个线程转账1000,
- 不使用synchronized,尝试使用AtomicIntegerFieldUpdater来实现。
代码如下:
class BankAccount//资源类
{
String bankName = "CCB";
//更新的对象属性必须使用 public volatile 修饰符。
public volatile int money = 0;//钱数
public void add() {
money++;
}
//因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
// 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
//不加synchronized,保证高性能原子性,局部微创小手术
public void transMoney() {
fieldUpdater.getAndIncrement(this);
}
}
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
//bankAccount.add();
bankAccount.transMoney();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t" + "result: " + bankAccount.money);
}
}
运行结果如下:
1.4.2 AtomicReferenceFieldUpdater
使用示例:
需求:
- 一个资源类,多个线程过来只有第一个能够对其进行初始化
class MyVar //资源类
{
public volatile Boolean isInit = Boolean.FALSE;
AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater =
AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
public void init(MyVar myVar) {
if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
System.out.println(Thread.currentThread().getName() + "\t" + "----- start init,need 2 seconds");
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "----- over init");
} else {
System.out.println(Thread.currentThread().getName() + "\t" + "----- 已经有线程在进行初始化工作。。。。。");
}
}
}
/**
* @auther zzyy
* 需求:
* 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,
* 要求只能被初始化一次,只有一个线程操作成功
*/
public class AtomicReferenceFieldUpdaterDemo {
public static void main(String[] args) {
MyVar myVar = new MyVar();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myVar.init(myVar);
}, String.valueOf(i)).start();
}
}
}
运行结果如下:
1.5 原子操作增强类!!!
- LongAdder
- LongAccumulator
- DoubleAdder
- DoubleAccumulator
1.5.1 LongAdder
LongAdder 只能用来计算加法,且从零开始计算。
常用API:
使用示例:
public class LongAdderAPIDemo {
public static void main(String[] args) {
LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.increment();
longAdder.increment();
System.out.println(longAdder.sum());
longAdder.add(-4);
System.out.println(longAdder.sum());
longAdder.reset();
System.out.println(longAdder.sum());
}
}
运行结果如下:
1.5.2 LongAccumulator
LongAccumulator 提供了自定义的函数操作。long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
使用示例:
public class LongAdderAPIDemo {
public static void main(String[] args) {
//LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
//{
// @Override
// public long applyAsLong(long left, long right)
// {
// return left * right;
// }
//},0);
//使用lambda表达式更加简洁,可以自定义加减乘除操作,以及初始值
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x / y, 5);
System.out.println(longAccumulator.get());
longAccumulator.accumulate(2);//2
longAccumulator.accumulate(2);//1
System.out.println(longAccumulator.get());
}
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bd4oFNrz-1682785708670)(http://blog.tempeisite.xyz/blog/image-20230428221452465.png)]
1.5.3 性能分析!!!
场景:热点商品点赞计算器,点赞数加加统计,不要求实时精确
需求:50个线程,每个线程100w次,计算总点赞数
class ClickNumber //资源类
{
int number = 0;
//方式1
public synchronized void clickBySynchronized() {
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
//方式2
public void clickByAtomicLong() {
atomicLong.getAndIncrement();
}
LongAdder longAdder = new LongAdder();
//方式3
public void clickByLongAdder() {
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
//方式4
public void clickByLongAccumulator() {
longAccumulator.accumulate(1);
}
}
/**
* 需求: 50个线程,每个线程100W次,总点赞数出来
*/
public class AccumulatorCompareDemo {
public static final int _1W = 10000;
public static final int threadNumber = 50;
public static void main(String[] args) throws InterruptedException {
ClickNumber clickNumber = new ClickNumber();
long startTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
}
}
运行结果:
由此可见:原子操作增强类的性能远比其他类更加高效。
1.5.3.1 LongAdder 为什么这么快
LongAdder 比 AtomicLong 性能更好,因为减少了乐观锁的重试次数。
架构:
LongAdder 是 Striped64 的子类
Striped64 这个类有几个比较重要的成员函数:
/** Number of CPUS, to place bound on table size CPU数量,即cells数组的最大长度 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
cells数组,为2的幂,2,4,8,16.....,方便以后位运算
*/
transient volatile Cell[] cells;
/**基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新。
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
/**创建或者扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁。
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int cellsBusy;
其中最重要的是如下这两个:
Cell 是 java.util.concurrent.atomic
下 Striped64
的一个内部类
Striped64 中一些变量或者方法的定义:
1.5.3.1.1 原因
LongAdder 的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的哪个值进行 CAS
操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回就可。
sum()会将所有Cell数组中的value和base累加作为返回值,核心思想就算将之前 AtomicLong
一个value的更新压力分散到多个value中去,从而降级更新热点。
base变量:在非竟态条件下,直接累加到该变量上。
Cell[]数组:竟态条件下,累加到各个线程自己的槽 Cell[i] 中
1.4.3.1.2 总结及源码分析
总结:
LongAdder 在无竞争的情况下,跟 AtomicLong 一样,对同一个 base/value 进行操作,当出现竞争关系时则是采用化整为零的做法。用空间换时间,用一个数组 cells ,将一个 value 拆分进这个数组 cells 。多个线程需要同时对 value 进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组 cells 的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组 cells 的所有值和无竞争值 base 都加起来作为最终结果。
LongAdder 的 add 方法解释:
- 如果cells表为空,尝试用 CAS 更新base字段,成功则退出。
- 如果cells表为空,CAS 更新base字段失败,出现竞争,uncontended为true,调用 longAccumulate 方法。
- 如果cells表非空,但当前线程映射的槽为空,uncontended为true,调用 longAccumulate 方法。
- 如果cells表非空,且当前线程映射的槽非空,CAS 更新Cell的值,成功则返回,否则,uncontended设为false,调用 longAccumulate 方法。
LongAdder 中 longAccumulate 方法:
1.5.4 总结对比
1.5.4.1 AtomicLong
特点:
- 线程安全,可允许一些性能损耗,要求高精度时可使用
- 保证精度,性能代价
- 多个线程针对单个热点值value进行原子操作
原理:
- CAS + 自旋
- incrementAndGet
场景:
- 低并发下的全局计算
- 能保证并发情况下计算的准确性,其内部通过 CAS 来解决并发安全性问题
缺陷:
- 高并发后性能急剧下降(N个线程CAS操作修改线程的值,每次只有一个成功,其他N-1个线程失败,失败的线程会不停的自旋直到成功,这样大量失败自旋的情况,会让cpu飙高)
1.5.4.2 LongAdder
特点:
- 当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
- 保证性能,精度代价
- LongAdder是每个线程都拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
原理:
- CAS + Base + Cell数组分散
- 空间换时间并分散了热点数据
场景:
- 高并发下的全局计算
缺陷:
- sum求和后还有计算线程修改结果的话,最后结果不够准确