1. 程式人生 > 其它 >執行緒基礎知識11-原子類

執行緒基礎知識11-原子類

1 簡介

  java中提供了一些原子類,原子類包裝了一個變數,並且提供了一系列對變數進行原子性操作的方法。我們在多執行緒環境下對這些原子類進行操作時,不需要加鎖,大大簡化了併發程式設計的開發。
  目前Java中提供的原子類大部分底層使用了CAS鎖(CompareAndSet自旋鎖),如AtomicInteger、AtomicLong等;也有使用了分段鎖+CAS鎖的原子類,如LongAdder等。

2 基本型別原子類

2.1 簡介

  包含以下三類,比較簡單。

    AtomicInteger

    AtomicBoolean

    AtomicLong

 

2.2 常用api

1)public final int get()

  獲取當前的值

 

2)public final int getAndSet(int newValue)

  獲取當前的值,並設定新的值

 

3)public final int getAndIncrement()

  獲取當前的值,並自增

 

4)public final int getAndDecrement()

  獲取當前的值,並自減

 

5)public final int getAndAdd(int delta)

  獲取當前的值,並加上預期的值

 

6)boolean compareAndSet(int expect, int update)

  如果輸入的數值等於預期值,則以原子方式將該值設定為輸入值(update)

 

2.3 示例

public class AtomicTest1 {

   static class MyNumber {
        @Getter
        private AtomicInteger atomicInteger = new AtomicInteger();
        public void addPlusPlus()
        {
            atomicInteger.incrementAndGet();
        }
    }
    
       public static void main(String[] args) throws InterruptedException
    {
        MyNumber myNumber 
= new MyNumber(); CountDownLatch countDownLatch = new CountDownLatch(100); for (int i = 1; i <=100; i++) { new Thread(() -> { try { for (int j = 1; j <=5000; j++) { myNumber.addPlusPlus(); } }finally { countDownLatch.countDown(); } },String.valueOf(i)).start(); } countDownLatch.await(); System.out.println(myNumber.getAtomicInteger().get()); } }

 

3 陣列型別原子類

3.1 簡介

   分為以下三類,api和基本型別原子類差不多

    AtomicIntegerArray

    AtomicLongArray

    AtomicReferenceArray

 

3.2 示例

public class AtomicTest2 {
    
     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(atomicIntegerArray.getAndSet(0,1122));
        System.out.println(atomicIntegerArray.get(0));
        System.out.println(atomicIntegerArray.getAndIncrement(1));
        System.out.println(atomicIntegerArray.get(1));
    }
}

 

4 引用型別原子類

4.1 簡介

  包含以下三類

    AtomicReference

    AtomicStampedReference

    AtomicMarkableReference

 

4.2 AtomicReference 編寫自旋鎖示例

public class AtomicTest3 {

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

    public void myLock()
    {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in");
        while(!atomicReference.compareAndSet(null,thread))
        {

        }
    }

    public void myUnLock()
    {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
    }

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

        new Thread(() -> {
            spinLockDemo.myLock();
            //暫停一會兒執行緒
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();
        //暫停一會兒執行緒,保證A執行緒先於B執行緒啟動並完成
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start();
    }
}

 

4.3 AtomicStampedReference 示例

  相較於AtomicReference,它攜帶版本號,可解決ABA問題

public class AtomicTest4 {

    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args)
    {
        abaProblem();
        abaResolve();
    }

    public static void abaResolve()
    {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 ----第1次stamp  "+stamp);
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println("t3 ----第2次stamp  "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("t3 ----第3次stamp  "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t4 ----第1次stamp  "+stamp);
            //暫停幾秒鐘執行緒
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem()
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicInteger.compareAndSet(100,20210308);
            System.out.println(atomicInteger.get());
        },"t2").start();
    }
}

 

4.4 AtomicMarkableReference

  和AtomicStampedReference相比,它不記錄版本號,只做一個標記,標記是否被修改過

public class AtomicTest5 {


    static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);

    public static void main(String[] args)
    {
        System.out.println("============AtomicMarkableReference不關心引用變數更改過幾次,只關心是否更改過======================");

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1次版本號"+marked);
            
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            
            markableReference.compareAndSet(100,101,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t 2次版本號"+markableReference.isMarked());
            
            markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t 3次版本號"+markableReference.isMarked());
        },"t5").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1次版本號"+marked);
           
            //暫停幾秒鐘執行緒
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            
            markableReference.compareAndSet(100,2020,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
        },"t6").start();
    }
}

 

5 物件的屬性修改原子類

5.1 簡介

  以一種執行緒安全的方式操作非執行緒安全物件內的某些欄位,但我們操作某些物件,只需要保證這個物件的某個或幾個屬性的操作的原子性,就可以使用它來,而不用去鎖定整個物件

  包含以下幾類

    AtomicIntegerFieldUpdater:原子更新物件中int型別欄位的值

    AtomicLongFieldUpdater:原子更新物件中Long型別欄位的值

    AtomicReferenceFieldUpdater:原子更新引用型別欄位的值

 

5.2 使用要求

  1)更新的物件屬性必須使用 public volatile 修飾符。

  2)因為物件的屬性修改型別原子類都是抽象類,所以每次使用都必須使用靜態方法newUpdater()建立一個更新器,並且需要設定想要更新的類和屬性。

 

5.3 AtomicIntegerFieldUpdater示例

public class AtomicTest6 {

    static class BankAccount {

        private String bankName = "CCB";//銀行

        public volatile int money = 0;//錢數  我們要操作的屬性 volatile修飾

        //兩個引數:操作的物件的class物件和操作的屬性名
        AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
        //不加鎖 + 效能高
        public void transferMoney(BankAccount bankAccount) {
            accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
        }
    }

    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount();
        for (int i = 1; i <= 1000; i++) {
            int finalI = i;
            new Thread(() -> {
                bankAccount.transferMoney(bankAccount);
            }, String.valueOf(i)).start();
        }

        //暫停毫秒,等待執行完成,也可以使用countdowmlatch
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(bankAccount.money);
    }
}

執行結果,結果是1000,正確

1000

 

5.4 AtomicIntegerFieldUpdater示例

public class AtomicTest7 {

    static class MyVar{
        public volatile Boolean isInit = Boolean.FALSE;
        AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");


        public void init(MyVar myVar)
        {
            if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
                //暫停幾秒鐘執行緒
                try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
            }else{
                System.out.println(Thread.currentThread().getName()+"\t"+"------其它執行緒正在初始化");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyVar myVar = new MyVar();
        for (int i = 1; i <=5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

執行結果

1    ---init.....
3    ------其它執行緒正在初始化
2    ------其它執行緒正在初始化
4    ------其它執行緒正在初始化
5    ------其它執行緒正在初始化
1    ---init.....over

Process finished with exit code 0

 

6 原子操作增強類

6.1 簡介

  包含以下幾類

    DoubleAccumulator:DoubleAccumulator提供了自定義的函式操作

    DoubleAdder:DoubleAdder只能用來計算加法,且從零開始計算

    LongAccumulator:LongAccumulator提供了自定義的函式操作

    LongAdder:LongAdder只能用來計算加法,且從零開始計算

 

6.2 示例

public class AtomicTest8 {

     public static void main(String[] args)
    {
        LongAdder longAdder = new LongAdder(); //預設是0開始

        longAdder.increment(); // +1
        longAdder.increment(); // +1
        longAdder.increment(); // +1
        System.out.println(longAdder.longValue());

        //-------------------------------------------------------------------

        //兩個引數,一個是自定義函式,一個是初始值,這裡傳入的函式式簡單的乘法
        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x * y,2);
        System.out.println(longAccumulator.longValue());

        longAccumulator.accumulate(1);
        System.out.println(longAccumulator.longValue());

        longAccumulator.accumulate(2);
        System.out.println(longAccumulator.longValue());

        longAccumulator.accumulate(3);
        System.out.println(longAccumulator.longValue());
    }
}

執行結果

3
2
2
4
12

 

6.3 效率對比示例

  50個執行緒,每個執行緒做100W次+1操作,比較synchronized、AtomicLong、LongAdder、LongAccumulator的執行效率

public class AtomicTest9 {

    //synchronized  =============================
    int number = 0;
    public synchronized void clickBySync()
    {
        number++;
    }

    //AtomicLong  ================================
    AtomicLong atomicLong = new AtomicLong(0);

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

    //LongAdder  =================================
    LongAdder longAdder = new LongAdder();
    public void clickByLongAdder()
    {
        longAdder.increment();
    }

    //LongAccumulator  ===================================
    LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
    public void clickByLongAccumulator()
    {
        longAccumulator.accumulate(1);
    }

    public static void main(String[] args) throws InterruptedException
    {
        AtomicTest9 clickNumberNet = new AtomicTest9();

        long startTime;
        long endTime;
        CountDownLatch countDownLatch = new CountDownLatch(50);
        CountDownLatch countDownLatch2 = new CountDownLatch(50);
        CountDownLatch countDownLatch3 = new CountDownLatch(50);
        CountDownLatch countDownLatch4 = new CountDownLatch(50);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=50; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * 10000; j++) {
                        clickNumberNet.clickBySync();
                    }
                }finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySync result: "+clickNumberNet.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=50; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * 10000; j++) {
                        clickNumberNet.clickByAtomicLong();
                    }
                }finally {
                    countDownLatch2.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong result: "+clickNumberNet.atomicLong);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=50; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * 10000; j++) {
                        clickNumberNet.clickByLongAdder();
                    }
                }finally {
                    countDownLatch3.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder result: "+clickNumberNet.longAdder.sum());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=50; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * 10000; j++) {
                        clickNumberNet.clickByLongAccumulator();
                    }
                }finally {
                    countDownLatch4.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator result: "+clickNumberNet.longAccumulator.longValue());
    }
}

執行結果

----costTime: 1774 毫秒     clickBySync result: 50000000
----costTime: 1053 毫秒     clickByAtomicLong result: 50000000
----costTime: 131 毫秒     clickByLongAdder result: 50000000
----costTime: 135 毫秒     clickByLongAccumulator result: 50000000

Process finished with exit code 0

發現LongAdder和LongAccumulator效率遠高於AtomicLong,AtomicLong高於synchronized

 

6.4 小結

6.3.1 AtomicLong

  原理:CAS+自旋

  場景:低併發下的全域性計算。AtomicLong能保證併發情況下計數的準確性,其內部通過CAS來解決併發安全性的問題。

  缺點:高併發後效能急劇下降。AtomicLong的自旋會成為瓶頸。

     N個執行緒CAS操作修改執行緒的值,每次只有一個成功過,其它N - 1失敗,失敗的不停的自旋直到成功,這樣大量失敗自旋的情況,一下子cpu就打高了。

 

6.3.2 LongAdder

  原理:CAS+Base+Cell陣列分散。空間換時間並分散了熱點資料

  場景:高併發下的全域性計算