併發安全工具類(6):原子類

1 什麼是原子類,有什麼作用?

  • 不可分割
  • 一個操作是不可中斷的,即便是多線程的情況下也可以保證
  • java.util.concurrent.atomic
  • 原子類的作用和鎖類似,是爲了保證併發情況下線程安全
  • 粒度更細:原子變量可以把競爭範圍縮小到變量級別,這是我們可以獲得的最細粒度的情況了,通常鎖的粒度都要大於原子變量的粒度(原子類相比於鎖,有一定的優勢
  • 效率更高:通常,使用原子類的效率會比使用鎖的效率更高,除了高度競爭的情況

2 6類原子類縱覽


3 Atomic*基本類型原子類,AtomicInteger爲例

  • AtomicInteger:整形原子類
  • AtomicLong:長整型原子類
  • AtomicBoolean:布爾型原子類

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) //獲取當前的值,並加上預期的值
  • public final boolean compareAndSet(int expect, int update) //如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入值(update)
/**
 * 演示AtomicInteger的基本用法,對比非原子類的線程安全問題,使用了原子類之後,不需要加鎖,也可以保證線程安全。
 */
public class AtomicIntegerDemo1 implements Runnable {
    private static final AtomicInteger atomicInteger = new AtomicInteger();
    private static volatile int basicCount = 0;
    public void incrementAtomic() {
        //正數就是每次都相加delta getAndAdd(1) == getAndIncrement()
        //負數就是每次都相減delta getAndAdd(-1) == getAndDecrement()
        atomicInteger.getAndAdd(1);
    }
    //public synchronized void incrementBasic() {
    //    basicCount++;
    //}
    public void incrementBasic() {
        basicCount++;
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo1 r = new AtomicIntegerDemo1();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("原子類的結果:" + atomicInteger.get());
        System.out.println("普通變量的結果:" + basicCount);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            incrementAtomic();
            incrementBasic();
        }
    }
}

4 AtomicBoolean

public class AtomicAtomicBooleanExample {
    private static AtomicBoolean isHappened = new AtomicBoolean(false);
    // 請求總數
    public static int clientTotal = 5000;
    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch begin = new CountDownLatch(1);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                try {
                    begin.await();
                    //System.out.println(Thread.currentThread().getName() + "開始執行test方法");
                    test();
                } catch (InterruptedException e) {
                    System.out.println("interruptedException");
                }
            });
        }
        Thread.sleep(3000);
        begin.countDown();
        executorService.shutdown();
        System.out.println("isHappened:" + isHappened.get());
    }
    private static void test() {
        if (isHappened.compareAndSet(false, true)) {
            //裏面的方法只會執行一次
            System.out.println("execute");
        }
    }
}

 


4 Atomic*Array數組類型原子類

/**
 * 演示原子數組的使用方法
 */
public class AtomicArrayDemo {
    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);
        Thread[] threadsIncrementer = new Thread[100];
        Thread[] threadsDecrementer = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threadsDecrementer[i] = new Thread(decrementer);
            threadsIncrementer[i] = new Thread(incrementer);
            threadsDecrementer[i].start();
            threadsIncrementer[i].start();
        }
        //Thread.sleep(10000);
        for (int i = 0; i < 100; i++) {
            try {
                threadsDecrementer[i].join();
                threadsIncrementer[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            //if (atomicIntegerArray.get(i)!=0) {
            //    System.out.println("發現了錯誤"+i);
            //}
            System.out.println(atomicIntegerArray.get(i));
        }
        System.out.println("運行結束");
    }
}

class Decrementer implements Runnable {
    private AtomicIntegerArray array;
    public Decrementer(AtomicIntegerArray array) {
        this.array = array;
    }
    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndDecrement(i);
        }
    }
}

class Incrementer implements Runnable {
    private AtomicIntegerArray array;
    public Incrementer(AtomicIntegerArray array) {
        this.array = array;
    }
    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndIncrement(i);
        }
    }
}

5 Atomic*Reference引用類型原子類

AtomicReference:AtomicReference類的作用,和AtomicInteger並沒有本質區別,AtomicInteger可以讓一個整數保證原子性,而AtomicReference可以讓一個對象保證原子性,當然,AtomicReference的功能明顯比AtomicInteger強,因爲一個對象裏可以包含很多屬性。用法和AtomicInteger類似。

@Slf4j
public class AtomicExample4 {
    private static AtomicReference<Integer> count = new AtomicReference<>(0);
    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());//4
    }
}

https://yuanyu.blog.csdn.net/article/details/104082917#t6 


6 把普通變量升級爲原子類:用AtomicIntegerFieldUpdater升級原有變量

  • AtomicIntegerFieldUpdater對普通變量進行升級
  • 使用場景:偶爾需要一個原子get-set操作
/**
 * 演示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);
    }
}
@Slf4j
public class AtomicExample5 {
    //原子性的去更新某一個類的實例指定的某一個字段
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
    //volatile修飾的非static的字段
    @Getter
    public volatile int count = 100;
    public static void main(String[] args) {
        AtomicExample5 example5 = new AtomicExample5();
        //如果example5裏面的count是100就更新爲120
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

 AtomicIntegerFieldUpdater注意點

  • 可見範圍:利用的反射修飾符不能是private
  • 不支持static:IllegalArgumentException

7 Adder累加器

  • 是Java 8引入的,相對是比較新的一個類
  • 高併發下LongAdder比AtomicLong效率高,不過本質是空間換時間
  • 競爭激烈的時候,LongAdder把不同線程對應到不同的Cell上進行修改,降低了衝突的概率,是多段鎖的理念,提高了併發性
  • 代碼演示
  • 這裏演示多線程情況下AtomicLong的性能
  • 由於競爭很激烈,每一次加法,都要flush和refresh,導致很耗費資源。

/**
 * 演示高併發場景下,LongAdder比AtomicLong性能好
 */
public class AtomicLongDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicLong counter = new AtomicLong(0);
        ExecutorService service = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while (!service.isTerminated()) {
        }
        long end = System.currentTimeMillis();
        System.out.println(counter.get());
        System.out.println("AtomicLong耗時:" + (end - start));//2229
    }
    private static class Task implements Runnable {
        private AtomicLong counter;
        public Task(AtomicLong counter) {
            this.counter = counter;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.incrementAndGet();
            }
        }
    }
}
/**
 * 演示高併發場景下,LongAdder比AtomicLong性能好
 */
public class LongAdderDemo {
    public static void main(String[] args) throws InterruptedException {
        LongAdder counter = new LongAdder();
        ExecutorService service = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while (!service.isTerminated()) {
        }
        long end = System.currentTimeMillis();
        System.out.println(counter.sum());
        System.out.println("LongAdder耗時:" + (end - start));//244
    }
    private static class Task implements Runnable {
        private LongAdder counter;
        public Task(LongAdder counter) {
            this.counter = counter;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        }
    }
}

在內部,這個LongAdder的實現原理和AtomicLong是有不同的,AtomicLong的實現原理是,每一次加法都需要做同步,所以在高併發的時候會導致衝突比較多,也就降低了效率

  • LongAdder,每個線程會有自己的一個計數器,僅用來在自己線程內計數,這樣一來就不會和其他線程的計數器干擾
  • 如圖中所示,第一個線程的計數器數值,也就是ctr′,爲1的時候,可能線程2的計數器ctr′′的數值已經是3了,他們之間並不存在競爭關係所以在加和的過程中,根本不需要同步機制,也不需要剛纔的flushrefresh。這裏也沒有—個公共的counter來給所有線程統一計數

LongAdder引入了分段累加的概念,內部有一個base變量和一個Cell[]數組共同參與計數

  • base變量:競爭不激烈,直接累加到該變量上
  • Cell[]數組:競爭激烈,各個線程分散累加到自己的槽Cell[i]中

sum源碼分析

//java.util.concurrent.atomic.LongAdder#sum
public long sum() {
	Cell[] as = cells; Cell a;
	long sum = base;
	if (as != null) {
		for (int i = 0; i < as.length; ++i) {
			if ((a = as[i]) != null)
				sum += a.value;
		}
	}
	return sum;
}

 對比AtomicLong和LongAdder

  • 在低爭用下,AtomicLong和LongAdder這兩個類具有相似的特徵。但是在競爭激烈的情況下,LongAdder的預期吞吐量要高得多,但要消耗更多的空間
  • LongAdder適合的場景是統計求和計數的場景,而且LongAdder基本只提供了add方法,而AtomicLong還具有cas方法

8 Accumulator累加器

Accumulator和Adder非常相似,Accumulator就是一個更通用版本的Adder

使用場景:大量並行計算,對計算順序不能有要求(不影響最後結果)

/**
 * 演示LongAccumulator的用法
 */
public class LongAccumulatorDemo {
    public static void main(String[] args) {
        //x是傳入的,y是初始值或者上一次計算的值
        LongAccumulator accumulator = new LongAccumulator((x, y) -> 2 + 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());
    }
}

 

發佈了537 篇原創文章 · 獲贊 2350 · 訪問量 204萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章