Java原子类
我們知道Java提供了兩種方式來處理線程安全的問題。第一種是互斥同步(悲觀鎖),第二種是采用非阻塞式同步(樂觀鎖)。雖然以上兩種方案都能解決線程安全的問題。但是在JDK1.5開始,就提供了java.util.concurrent.atomic包,這個包中的原子操作類提供了更為簡單高效、線程安全的方式來更新一個變量的值。例如AtomicBoolean、AtomicLong、AtomicInteger等。(這里提到的Atomic系列類原理都是CAS操作)
一、原子類作用及類型
作用:由Atomic可知,這些類提供的操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾。
類型:
二、基本類型的線程同步綜合例子(static、原子類、synchronized修飾的類、volatile)
public class AtomicClassTest {public static int count = 0;//synchronized修飾的類public static Counter counter = new Counter();public static AtomicInteger atomicInteger = new AtomicInteger(0);volatile public static int countVolatile = 0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {count++;counter.increment();atomicInteger.getAndIncrement();countVolatile++;}}}.start();}try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("static count: " + count);System.out.println("Counter(synchronized修飾的類): " + counter.getValue());System.out.println("AtomicInteger(原子類): " + atomicInteger.intValue());System.out.println("countVolatile(volatitle修飾 ): " + countVolatile);}} class Counter {private int value;public synchronized int getValue() {return value;}public synchronized int increment() {return ++value;}public synchronized int decrement() {return --value;} }
通過上面的例子說明,要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,或者使用synchronized同步方法。而通過Volatile關鍵字,并不能解決非原子操作的線程安全性
順帶說一下Java中的自增原理:自增并不是一個原子操作(這是一個“讀取 - 修改 - 寫入”的操作序列,并且其結果狀態依賴于之前的狀態),這也是volatile在上面例子不起作用的原因。(因為volatile只是保證操作都是在主存中進行,可是并不保證線程的互斥和原子性,而這里的count++操作是先 int temp=count;然后 count=count+1;然后寫入主存。這里是有3個操作。當A線程進行int temp=count操作的時候,此時切換到另一個線程也進行這個操作,那么從主存中讀取出來的值是一樣的,然后在自己的線程內部進行操作,那么進行到最后的寫入的時候,寫入的值就是一樣的了。所以由于i++有三個操作,而volatile只能保證內存可見性。導致了線程安全問題的出現。)(不要將volatile用在getAndOperate場合(這種場合不原子,需要再加鎖),僅僅set或者get的場景是適合volatile的)
三、數組類型原子類
對于數組類型的原子類,在Java中,主要通過原子的方式更新數組里面的某個元素,以AtomicIntegerArray為例,因為其內部原理都是循環CAS操作,所以我們這里就描述其使用方式,具體代碼如下:
public class AtomicArray {private int[] value = new int[]{0, 1, 2};private AtomicIntegerArray mAtomicIntegerArray = new AtomicIntegerArray(value);private void doAdd() {for (int i = 0; i < 5; i++) {int value = mAtomicIntegerArray.addAndGet(0, 1);System.out.println(Thread.currentThread().getName() + "--->" + value);}}public static void main(String[] args) {AtomicArray demo = new AtomicArray();new Thread(demo::doAdd, "線程1").start();new Thread(demo::doAdd, "線程2").start();} }四、引用類型原子類
我們知道CAS雖然很高效,但是它也存在三大問題,這里也簡單說一下:
CAS操作如果長時間不成功,會導致其一直自旋,給CPU帶來非常大的開銷。
使用AtomicReference解決多變量共享問題:
public class CAS_Multiple {Person mPerson = new Person("紅紅", 1);private AtomicReference<Person> mAtomicReference = new AtomicReference<>(mPerson);private class Person {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}}private void updatePersonInfo(String name, int age) throws Exception {System.out.println(Thread.currentThread().getName() + "更新前--->" + mAtomicReference.get().name + "---->" + mAtomicReference.get().age);mAtomicReference.getAndUpdate(person -> new Person(name, age));}public static void main(String[] args) throws InterruptedException {CAS_Multiple demo = new CAS_Multiple();new Thread(() -> {try {demo.updatePersonInfo("藍藍", 2);} catch (Exception e) {e.printStackTrace();}}, "線程1").start();Thread.sleep(1000);System.out.println("暫停一秒--->" + demo.mAtomicReference.get().name + "---->" + demo.mAtomicReference.get().age);System.out.println("更新后---->" + demo.mAtomicReference.get().name + "---->" + demo.mAtomicReference.get().age);} } //輸出結果 線程1更新前--->紅紅---->1 暫停一秒--->藍藍---->2 更新后---->藍藍---->2AtomicStampedReference解決ABA問題:
public class ABA_AtomicStampedReference {Person mPerson = new Person("紅紅", 1);private AtomicStampedReference<Person> mAtomicReference = new AtomicStampedReference<>(mPerson, 1);private class Person {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}}/*** 更新信息* @param name 名稱* @param age 年齡* @param oldStamp CAS操作比較的舊的版本* @param newStamp 希望更新后的版本*/private void updatePersonInfo(String name, int age, int oldStamp, int newStamp) {System.out.println(Thread.currentThread().getName() + "更新前--->" + mAtomicReference.getReference().name + "---->" + mAtomicReference.getReference().age);mAtomicReference.compareAndSet(mPerson, new Person(name, age), oldStamp, newStamp);}public static void main(String[] args) throws Exception {ABA_AtomicStampedReference demo = new ABA_AtomicStampedReference();new Thread(() -> demo.updatePersonInfo("藍藍", 2, 1, 2), "線程1").start();Thread.sleep(1000);System.out.println("暫停一秒--->" + demo.mAtomicReference.getReference().name + "---->" + demo.mAtomicReference.getReference().age);new Thread(() -> demo.updatePersonInfo("花花", 3, 1, 3), "線程2").start();Thread.sleep(1000);System.out.println("更新后---->" + demo.mAtomicReference.getReference().name + "---->" + demo.mAtomicReference.getReference().age);} } //結果 線程1更新前--->紅紅---->1 暫停一秒--->藍藍---->2 線程2更新前--->藍藍---->2 更新后---->藍藍---->2在上述代碼中,我們使用AtomicStampedReference類,其中在使用該類的時候,需要傳入一個類似于版本(你也可以叫做郵戳,時間戳等,隨你喜歡)的int類型的屬性。在Main方法中我們分別創建了2個線程來進行CAS操作,其中線程1想做的操作是將版本為1的mPerson(“紅紅”,1)修改為版本為2的Person(“藍藍,2”)。當線程1執行完畢后,緊接著線程2開始執行,線程2想做的操作是將版本為1的mPerson(“紅紅”,1)修改為版本3的Person(“花花”,3)。從程序輸出結果可以看出,線程2的操作是沒有執行的。也就驗證了AtomicStampedReference確實解決了ABA的問題。
五、字段類型原子類
如果需要更新某個類中的某個字段,在Actomic系列中,Java提供了3個類來實現,原理都大同小異,這里我們以AtomicIntegerFieldUpdate類來講解,具體代碼如下:
public class Test_AtomicIntegerFieldUpdate {Person mPerson = new Person("紅紅", 1);private AtomicIntegerFieldUpdater<Person> mFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");private class Person {String name;volatile int age;//使用volatile修飾Person(String name, int age) {this.name = name;this.age = age;}}/*** 更新信息* @param age 年齡*/private void updatePersonInfo(int age) {System.out.println("更新前--->" + mPerson.age);mFieldUpdater.addAndGet(mPerson, age);}private int getUpdateInfo() {return mFieldUpdater.get(mPerson);}public static void main(String[] args) throws Exception {Test_AtomicIntegerFieldUpdate demo = new Test_AtomicIntegerFieldUpdate();new Thread(() -> demo.updatePersonInfo(12), "線程1").start();Thread.sleep(1000);System.out.println("更新后--->" + demo.getUpdateInfo());} } //結果 更新前--->1 更新后--->13這里對AtomicIntegerFieldUpdate不在進行過多的描述,大家需要主要的是在使用字段類型原子類的時候,需要進行更新的字段,需要通過volatile來修飾。
六、總結
本文轉載
總結
- 上一篇: 原子操作的实现原理(锁和循环CAS)
- 下一篇: 产品经理是打杂的?