JUC---原子类Atomic*.java源码解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


一、什么是原子类

  • 不可分割性
  • 一个操作是不可中断的,在多线程下也可以保证
  • 相比于锁,原子变量可以把竞争资源缩小到变量级别,粒度更细。
  • 通常情况下比锁的效率更高,但是在高度竞争的情况下性能更低。

以AtomicInteger为例,看看它的方法

int get();//获取到当前值
int getAndSet(int newValue) {//获取到当前值并设置新的值
    return U.getAndSetInt(this, VALUE, newValue);
}
int getAndIncrement() {//获取到当前值并自增
    return U.getAndAddInt(this, VALUE, 1);
}
int getAndDecrement() {//获取到当前值并自减
    return U.getAndAddInt(this, VALUE, -1);
}
int getAndAdd(int delta) {//获取到当前值并增加delta
    return U.getAndAddInt(this, VALUE, delta);
}
//最重要的CAS操作:如果当前的值是expectedValue,则设置当前值为newValue
boolean compareAndSet(int expectedValue, int newValue) {
    return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}

为什么是线程安全的:

public final int getAndIncrement() {
  return U.getAndAddInt(this, VALUE, 1);
}
//getAndIncrement方法就是调用了Unsafe类的getAndAddInt方法,并且通过do while循环自旋CAS。从而保证原子性。
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

常用的原子类和原子数组的实例代码Demo在我的GitHub上:源码


二、原子引用类AtomicReference

AtomicReference的作用:和AtomicInteger效果一样,AtomicInteger可以让一个整数原子性,AtomicReference可以让一个对象保证原子性。

看看源码,也是CAS,熟悉的味道:
在这里插入图片描述
利用AtomicReference实现自旋锁:

public class SpinLock {

    private AtomicReference<Thread> sign = new AtomicReference<>();

    public void lock() {
        Thread current = Thread.currentThread();
        // 当前没有线程获取到锁的情况下才会跳出while循环,自旋操作
        while (!sign.compareAndSet(null, current)) {
            System.out.println("自旋获取失败,再次尝试");
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        // 释放当前线程的锁
        sign.compareAndSet(current, null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "释放了自旋锁");
                }
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}

三、AtomicIntegerFieldUpdater把普通变量升级为原子变量

public class AtomicIntegerFieldUpdaterDemo implements Runnable{

    static Candidate tom;
    static Candidate peter;

    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater
            .newUpdater(Candidate.class, "score");

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            peter.score++;
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static class Candidate {
        volatile int score;
    }

    public static void main(String[] args) throws InterruptedException {
        tom=new Candidate();
        peter=new Candidate();
        AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("普通变量:"+peter.score);
        System.out.println("升级后的结果"+ tom.score);
    }
}

四、LongAdder累加器

  • 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
  • 竞争激烈的时候,LongAdder把不同线程对应到不同cell上进行修改,降低了冲突概率,利用了多段锁的概念。提高了并发。

AtomicLong为什么更慢:
在这里插入图片描述
因为它每一次操作都需要flush和refresh。每一次修改了值都需要把当前值从线程缓存刷到主内存,再同步到每一个线程的内存。在core1中修改了值,需要把值从local cache flush到共享内存(主内存),再从主内存refresh到每个线程的local cache。

LongAdder的改进:
在这里插入图片描述
竞争激烈的情况下,每个线程在自己的内存中计算,互相不干扰,也不需要同步每一次修改过后的值。只需要在计算结束后把每个线程的结果加起来。LongAdder采用分段累加,内部有一个base变量,和一个Cell[]数组。

  • 竞争不激烈的情况直接累加到base变量
  • 竞争激烈的情况,各个线程分散累加到自己的cell[i]槽中
    其本质就是空间换时间,
public void add(long x) {
   Cell[] cs; long b, v; int m; Cell c;
   // 先尝试casBase
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||//当前线程分配的cell[]槽
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
    
public long sum() {
   Cell[] cs = cells;
     long sum = base;
     if (cs != null) {
         for (Cell c : cs)
             if (c != null)
                 sum += c.value;
     }
     return sum;
 }

五、Accumulator累加器

适用于大量计算、并行计算的场景。

public class LongAccumulatorDemo {

    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator((x, y) -> 2 + x * y, 1);
        //LongAccumulator accumulator = new LongAccumulator((x, y) -> Math.max(x, y), 1);
        ExecutorService executor = Executors.newFixedThreadPool(8);
        IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));

        executor.shutdown();
        while (!executor.isTerminated()) {

        }
        System.out.println(accumulator.getThenReset());
    }
}

  • 我的公众号:Coding抠腚
  • 偶尔发发自己最近学到的干货。学习路线,经验,技术分享。技术问题交流探讨。
    Coding抠腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章