Java并发包下Atomic相关类的使用

483 阅读4分钟

java.util.concurrent.atomic 分类:

  • 基本类型原子类
    • AtomicInteger
    • AtomicLong
    • AtomicBoolean
  • 数组类型原子类
    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
  • 引用类型原子类
    • AtomicReference
    • AtomicMarkableReference
    • AtomicStampedReference
  • 对象的属性修改原子类
    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater
    • AtomicReferenceFieldUpdater
  • 原子操作增强类
    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

AtomicStampedReference 和 AtomicMarkableReference 区别

AtomicStampedReference是携带版本号的引用类型原子类,可以解决ABA问题,记录的是修改过几次

AtomicMarkableReference是将AtomicStampedReference的版本号,简化为true或false,并且只能使用一次

使用示例

    1. 基本类型原子类,以AtomicInteger为例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest {

    /**
     * 50个线程
     */
    private static final int THREAD_NUM = 50;
    private static final CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
                        myNumber.add();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println("最终结果:" + myNumber.num.get());
    }

}

class MyNumber {
    AtomicInteger num = new AtomicInteger();

    public void add() {
        num.getAndIncrement();
    }
}
    1. 数组类型原子类,以AtomicIntegerArray为例
public static void main(String[] args) {
    AtomicIntegerArray atomicIntegerArray1 = new AtomicIntegerArray(5);
    AtomicIntegerArray atomicIntegerArray2 = new AtomicIntegerArray(new int[5]);
    AtomicIntegerArray atomicIntegerArray3 = new AtomicIntegerArray(new int[]{1, 2, 3, 4, 5});

    for (int i = 0; i < atomicIntegerArray3.length(); i++) {
        // 输出 1、2、3、4、5
        System.out.println(atomicIntegerArray3.get(i));
    }

    int temp = 0;
    atomicIntegerArray3.getAndSet(0, 111);
    // 输出 111
    System.out.println(atomicIntegerArray3.get(0));

    // +1
    atomicIntegerArray3.getAndIncrement(1);
    // +1
    atomicIntegerArray3.getAndIncrement(1);
    // 输出 4
    System.out.println(atomicIntegerArray3.get(1));
}
    1. 引用类型原子类,以AtomicReference为例实现一个自旋锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void MyLock() {
        System.out.println(Thread.currentThread().getName() + " come in");
        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {

        }
        System.out.println(Thread.currentThread().getName() + " 获取锁成功");
    }

    public void MyUnLock() {
        while (!atomicReference.compareAndSet(Thread.currentThread(), null)) {

        }
        System.out.println(Thread.currentThread().getName() + " 释放锁");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            // 获取锁
            spinLockDemo.MyLock();
            try {
                // 拿到锁使用3秒钟
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 释放锁
            spinLockDemo.MyUnLock();
        }, "t1").start();

        new Thread(() -> {
            spinLockDemo.MyLock();
            spinLockDemo.MyUnLock();
        }, "t2").start();
    }

}

输出结果

t1 come in
t1 获取锁成功
t2 come in
t1 释放锁
t2 获取锁成功
t2 释放锁
    1. 对象的属性修改原子类,是以一种线程安全的方式操作非线程安全对象内的某些字段

    使用要求:更新的对象属性必须使用 public volatile 进行修饰

AtomicIntegerFieldUpdater使用示例

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterDemo {

    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                bankAccount.doAction(bankAccount);
            }).start();
        }
        TimeUnit.SECONDS.sleep(1);
        // 输出1000
        System.out.println(bankAccount.money);
    }

}

class BankAccount {
    public String bankName = "中国银行";
    public volatile int money;

    // 通过反射获取对象
    AtomicIntegerFieldUpdater<BankAccount> updater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");

    public void doAction(BankAccount bankAccount) {
        updater.incrementAndGet(bankAccount);
    }
}
    1. 原子操作增强类

LongAdder:只能用来计算加法,且从零开始计算

LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.increment();
longAdder.increment();
// 输出3
System.out.println(longAdder.longValue());

LongAccumulator:提供了自定义的函数操作

LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
longAccumulator.accumulate(1);
longAccumulator.accumulate(2);
longAccumulator.accumulate(3);
// 输出6
System.out.println(longAccumulator.longValue());

Atomic高性能对比

示例:使用50个线程,每个线程累加100万次,最后输出结果(类似高并发点赞功能)

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

class ClickNumber {
    int number = 0;

    public synchronized void addBySynchronized() {
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void addByAtomicInteger() {
        atomicInteger.incrementAndGet();
    }

    AtomicLong atomicLong = new AtomicLong(0);

    public void addByAtomicLong() {
        atomicLong.incrementAndGet();
    }

    LongAdder longAdder = new LongAdder();

    public void addByLongAdder() {
        longAdder.increment();
    }

    LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

    public void addByLongAccumulator() {
        longAccumulator.accumulate(1);
    }
}

public class LongAdderDemo {

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long startTime;
        long endTime;

        CountDownLatch countDownLatch1 = new CountDownLatch(50);
        CountDownLatch countDownLatch2 = new CountDownLatch(50);
        CountDownLatch countDownLatch3 = new CountDownLatch(50);
        CountDownLatch countDownLatch4 = new CountDownLatch(50);
        CountDownLatch countDownLatch5 = new CountDownLatch(50);

        startTime = System.currentTimeMillis();
        // 50个线程
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000000; j++) {
                        // 每个线程累加100万次
                        clickNumber.addBySynchronized();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("synchronized 耗时 " + (endTime - startTime) + " 毫秒 " + clickNumber.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000000; j++) {
                        clickNumber.addByAtomicInteger();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("AtomicInteger 耗时 " + (endTime - startTime) + " 毫秒 " + clickNumber.atomicInteger.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000000; j++) {
                        clickNumber.addByAtomicLong();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("AtomicLong 耗时 " + (endTime - startTime) + " 毫秒 " + clickNumber.atomicLong.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000000; j++) {
                        clickNumber.addByLongAdder();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("LongAdder 耗时 " + (endTime - startTime) + " 毫秒 " + clickNumber.longAdder.longValue());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000000; j++) {
                        clickNumber.addByLongAccumulator();
                    }
                } finally {
                    countDownLatch5.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch5.await();
        endTime = System.currentTimeMillis();
        System.out.println("LongAccumulator 耗时 " + (endTime - startTime) + " 毫秒 " + clickNumber.longAccumulator.longValue());
    }

}

从输出结果可见,LongAdder性能明显高于其它的

synchronized 耗时 941 毫秒 50000000
AtomicInteger 耗时 980 毫秒 50000000
AtomicLong 耗时 984 毫秒 50000000
LongAdder 耗时 153 毫秒 50000000
LongAccumulator 耗时 262 毫秒 50000000

LongAdder为什么比AtomicLong快?

  1. 官方文档上的解释是,在线程数竞争较少的情况下,AtomicLong和LongAdder类似,但是在更高并发情况下,LongAdder的吞吐量明显更高,但是代价是更高的空间消耗。这也就是空间换时间

  2. AtomicIntegerAtomicLong 父类是 Number 。而 LongAdder 父类是 Striped64

public class AtomicLong extends Number

public class LongAdder extends Striped64

abstract class Striped64 extends Number

Striped64 源码中,有如下定义

/**
 * Number of CPUS, to place bound on table size
 * CPU 核心数量
 */
static final int NCPU = Runtime.getRuntime().availableProcessors();

/**
 * Table of cells. When non-null, size is a power of 2.
 * Cell 数组,为2的幂,2,4,8,16.....方便位运算,上限就是CPU核心数量
 */
transient volatile Cell[] cells;

/**
 * Base value, used mainly when there is no contention, but also as
 * a fallback during table initialization races. Updated via CAS.
 * 基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新
 */
transient volatile long base;

/**
 * Spinlock (locked via CAS) used when resizing and/or creating Cells.
 * 创建或者扩容cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁
 */
transient volatile int cellsBusy;

LongAdder 的基本思路就是分散热点,将value值分散到一个 Cell 数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小了很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回。

sun() 会将所有 Cell 数组中的 value 和 base 累积作为返回值,核心的思想是将之前 AtomicLong 一个 value 的更新压力分散到多个 value 中去,从而降低更新热点。

image.png

sun求和公式: Value=Base+inCell[i]Value = Base + \sum_i^nCell[i]

LongAdder 缺陷:sum求和后还有其它线程修改了结果的话,最后结果不够准确

LongAdder 在无竞争的情况下,跟 AtomicLong一样,对同一个 base 进行操作,当出现竞争关系时则是采用化整为零的做法,用空间换时间,用一个数组cells,将一个value拆分进这个数组cells。

多个线程需要同时对 value 进行操作的时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。

当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。