JDK 1.8 atomic 包

只談?wù)劊蝗采w

CAS

悲觀鎖,假設(shè)執(zhí)行當(dāng)前區(qū)域代碼都會產(chǎn)生沖突,為了解決沖突,線程A獲取鎖時,其它線程會被阻塞處于停頓狀態(tài),直到鎖釋放(可能會造成死鎖)。CAS概念屬于一種樂觀鎖的策略(或者稱為無鎖),假設(shè)執(zhí)行當(dāng)前區(qū)域代碼都不會發(fā)生沖突,不會發(fā)生沖突就自然沒有線程阻塞,也不會產(chǎn)生死鎖問題。如果出現(xiàn)了沖突,CAS(compare and swap) 比較和替換就會不斷的去重試,直到當(dāng)前操作沒有沖突。

CAS 的 ABA 問題

  1. 線程1從內(nèi)存M位置取出A
  2. 線程2從內(nèi)存M位置取出A
  3. 線程2做了預(yù)期值比較,將A替換為B并放到M位置
  4. 線程2從內(nèi)存M位置取出B
  5. 線程2做了預(yù)期值比較,將B替換為A并放到M位置
  6. 線程1做了預(yù)期值比較,將A替換為C并放到M位置

此時線程1影響了線程2的狀態(tài),也就發(fā)生了ABA的問題。所以為了解決樂觀鎖并發(fā)時造成的ABA問題,都會使用AtomicStampedReference 類或者AtomicMarkableReference 類

volatile

volatile從主內(nèi)存中加載到線程工作內(nèi)存中的值是最新的。也就是說它解決的是多線程并發(fā)變量讀時的可見性問題,但無法保證訪問變量的原子性。而且volatile只能修飾變量。

原子基本類型

  • AtomicBoolean 保證布爾值的原子性
  • AtomicInteger 保證整型的原子性
  • AtomicLong 保證長整型的原子性

原子數(shù)組

  • AtomicIntegerArray 保證整型數(shù)組的原子性
  • AtomicLongArray 保證長整型數(shù)組原子性

原子字段

  • AtomicIntegerFieldUpdater 保證整型的字段更新
  • AtomicLongFieldUpdater 保證長整型的字段更新

使用原子字段類時,所聲明的字段類型必須為volatile

使用方法如下:

    private int sum = 100;
    private volatile int sum1 = 100;
    // 當(dāng) sum 或 sum1 為100 時只允許有一個線程進(jìn)入
    private void atomic4() {
        AtomicIntegerFieldUpdater<T> tAtomicIntegerFieldUpdater =  AtomicIntegerFieldUpdater.newUpdater(T.class, "sum1");
        T t = new T();
        for (int i = 0; i < 10; i++) {
            singleThreadPool.execute(() -> {
                if (sum == 100) {
                    System.out.println(Thread.currentThread().getName() + " :" + "已修改");
                }
            });
        }

        System.out.println("=====");

        for (int i = 0; i < 10; i++) {
            singleThreadPool.execute(() -> {
                if(tAtomicIntegerFieldUpdater.compareAndSet(t, 100, 120)){
                    System.out.print(Thread.currentThread().getName() + " :" + "tAtomicIntegerFieldUpdater 已修改");
                }
            });
        }
        singleThreadPool.shutdown();
    }

原子引用類型

  • AtomicReferenceArray 保證引用數(shù)組的原子性
  • AtomicReferenceFieldUpdater 保證引用類型的字段更新
  • AtomicStampedReference 可以解決CASABA問題,類似提供版本號
  • AtomicMarkableReference 可以解決CASABA問題,提供是或否進(jìn)行判斷

原子累加器 JDK 1.8 新增

原有的 Atomic系列類通過CAS來保證并發(fā)時操作的原子性,但是高并發(fā)也就意味著CAS的失敗次數(shù)會增多,失敗次數(shù)的增多會引起更多線程的重試,最后導(dǎo)致AtomicLong的效率降低。新的四個類通過減少并發(fā),將單一value的更新壓力分擔(dān)到多個value中去,降低單個value的“熱度”以提高高并發(fā)情況下的吞吐量

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

實(shí)例應(yīng)用

  • 只貼了代碼片段。驗證累加器、整型、數(shù)組的原子性
package concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 張博
 */
public class ThreadFactoryBuilder implements ThreadFactory {

    private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    ThreadFactoryBuilder() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY) {
            t.setPriority(Thread.NORM_PRIORITY);
        }
        return t;
    }
}

private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder();
    private static ExecutorService singleThreadPool = new ThreadPoolExecutor(1000, 5000,
            10, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    // 阻塞主線程,循環(huán)5000次后通知主線程關(guān)閉
    private CountDownLatch begin = new CountDownLatch(5000);
    // 模擬并發(fā)量一次200
    private Semaphore semaphore = new Semaphore(200);
    private static int count = 0;
    private void atomic7() {
        DoubleBinaryOperator doubleBinaryOperator = (x, y) -> x + y;
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator(doubleBinaryOperator, count);
        AtomicInteger atomicInteger = new AtomicInteger(0);
        int[] ints = new int[5000];
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5000);
        for (int i = 0; i < 5000; i++) {
            singleThreadPool.execute(() -> {
                try {
                    // 允許200個線程進(jìn)入,模擬提供穩(wěn)定并發(fā)量
                    semaphore.acquire();
                    count();
                    atomicCount(doubleAccumulator);
                    atomicIntegerCount(atomicInteger);
                    array(ints);
                    atomicArray(atomicIntegerArray);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 每執(zhí)行一次減1
                begin.countDown();
            });
        }
        try {
            // 沒到0一直等待,直到模擬并發(fā)結(jié)束
            begin.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        singleThreadPool.shutdown();
        System.out.println(count);
        System.out.println(doubleAccumulator.get());
        System.out.println(atomicInteger.get());
        System.out.println(Arrays.toString(ints));
        System.out.println("=========================================================================================================");
        System.out.println(atomicIntegerArray.toString());
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            if ((atomicIntegerArray.get(i) - 5000) != 0) {
                System.out.println(atomicIntegerArray.get(i));
            }
        }
    }

    /**
     * 時間:2018/8/3 上午11:48
     * @apiNote 線程不安全的累加
     */
    private void count() {
        count++;
    }

    /**
     * 時間:2018/8/3 上午11:48
     * @apiNote 原子累加
     */
    private void atomicCount(DoubleAccumulator doubleAccumulator) {
        doubleAccumulator.accumulate(1);
    }

    /**
     * 時間:2018/8/3 上午11:48
     * @apiNote 原子的i++
     */
    private void atomicIntegerCount(AtomicInteger atomicInteger) {
        atomicInteger.incrementAndGet();
    }

    /**
     * 時間:2018/8/3 上午11:48
     * @apiNote 線程不安全的數(shù)組操作
     */
    private void array(int[] ints) {
        for(int k = 0; k < 5000; k++) {
            ints[k] += 1;
        }
    }

    /**
     * 時間:2018/8/3 上午11:48
     * @apiNote 原子的數(shù)組操作
     */
    private void atomicArray(AtomicIntegerArray atomicIntegerArray) {
        for(int k = 0; k < 5000; k++) {
            atomicIntegerArray.addAndGet(k, 1);
        }
    }

使用AtomicStampedReference解決CASABA問題

    /**
     * 時間:2018/8/3 上午11:58
     * @apiNote 模擬并發(fā)導(dǎo)致 CAS 的 ABA 問題
     */
    private void aba() {
        // 原子引用類型
        AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");
        // 原子時間戳引用
        AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A", 0);

        // 線程1
        singleThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "B"));
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("B", "A"));
            System.out.println("=====");
        });

        // 線程2
        singleThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "C"));
            System.out.println("=====");
        });

        // 線程3
        singleThreadPool.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "B", stampedReference.getStamp(), stampedReference.getStamp() + 1));
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("B", "A", stampedReference.getStamp(), stampedReference.getStamp() + 1));
            System.out.println("=====");
        });

        // 線程4
        singleThreadPool.execute(() -> {
            // 模擬并發(fā)時與線程3同時得到內(nèi)存中的A的時間戳
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " : 線程4 處理 cas 之前" + stamp);
            // 線程4休眠2秒,模擬讓線程3已經(jīng)操作完成 A -> B -> A 的 CAS
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 得到線程3操作完的時間戳
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.getStamp());
            // 線程4進(jìn)行 A -> C 的 CAS 操作。這時會失敗。解決 ABA 問題
            System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "C", stamp, stamp + 1));
        });

        singleThreadPool.shutdown();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,726評論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,341評論 11 349
  • 關(guān)于哲同學(xué)使用手機(jī)的問題上經(jīng)常很糾結(jié),和哲也常發(fā)生沖突。對于玩手機(jī)的事我們和哲一直都不能很好的達(dá)成共識。 在商定孩...
    胡胡_9e10閱讀 147評論 0 0