Atomic包的作用
方便程序員在多線程環境下,無鎖的進行原子操作
Atomic包核心
Atomic包里的類基本都是使用Unsafe實現的包裝類,核心操作是CAS原子操作;
關於CAS
compare and swap,比較和替換技術,將預期值與當前變量的值比較(compare),如果相等則使用新值替換(swap)當前變量,否則不作操作;
現代CPU已廣泛支持CAS指令,如果不支持,那么JVM將使用自旋鎖,與互斥鎖一樣,兩者都需先獲取鎖才能訪問共享資源,但互斥鎖會導致線程進入睡眠,而自旋鎖會一直循環等待直到獲取鎖;
另外,有一點需要注意的是CAS操作中的ABA問題,即將預期值與當前變量的值比較的時候,即使相等也不能保證變量沒有被修改過,因為變量可能由A變成B再變回A,解決該問題,可以給變量增加一個版本號,每次修改變量時版本號自增,比較的時候,同時比較變量的值和版本號即可;
Atomic包主要提供四種原子更新方式
- 原子方式更新基本類型;
- 原子方式更新數組;
- 原子方式更新引用;
- 原子方式更新字段;
原子方式更新基本類型
以下三個類是以原子方式更新基本類型
- AtomicBoolean:原子更新布爾類型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新長整型。
以AtomicInteger為例,
package concurrency; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { //相當於i++,返回的是舊值,看方法名就知道,先獲取再自增 System.out.println(ai.getAndIncrement()); System.out.println(ai.get()); //先自增,再獲取 System.out.println(ai.incrementAndGet()); System.out.println(ai.get()); //增加一個指定值,先add,再get System.out.println(ai.addAndGet(5)); System.out.println(ai.get()); //增加一個指定值,先get,再set System.out.println(ai.getAndSet(5)); System.out.println(ai.get()); } }
注意:Atomic包提供了三種基本類型的原子更新,剩余的Java的基本類型還有char,float和double等,其更新方式可以參考AtomicBoolean的思路來現,AtomicBoolean是把boolean轉成整型再調用compareAndSwapInt進行CAS來實現的,類似的short和byte也可以轉成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits轉成整形和長整形進行相應處理;
原子方式更新數組
以下三個類是以原子方式更新數組,
- AtomicIntegerArray:原子更新整型數組里的元素。
- AtomicLongArray:原子更新長整型數組里的元素。
- AtomicReferenceArray:原子更新引用類型數組里的元素。
以AtomicIntegerArray為例,其方法與AtomicInteger很像,多了個數組下標索引;
package concurrency; import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest { static int[] valueArr = new int[] { 1, 2 }; //AtomicIntegerArray內部會拷貝一份數組 static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr); public static void main(String[] args) { ai.getAndSet(0, 3); //不會修改原始數組value System.out.println(ai.get(0)); System.out.println(valueArr[0]); } }
原子方式更新引用
以下三個類是以原子方式更新引用,與其它不同的是,更新引用可以更新多個變量,而不是一個變量;
- AtomicReference:原子更新引用類型。
- AtomicReferenceFieldUpdater:原子更新引用類型里的字段。
- AtomicMarkableReference:原子更新帶有標記位的引用類型。
以AtomicReference為例,
package concurrency; import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest { public static AtomicReference<User> atomicUserRef = new AtomicReference<User>(); public static void main(String[] args) { User user = new User("conan", 15); atomicUserRef.set(user); User updateUser = new User("Shinichi", 17); atomicUserRef.compareAndSet(user, updateUser); System.out.println(atomicUserRef.get().getName()); System.out.println(atomicUserRef.get().getOld()); } static class User { private String name; private int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
原子方式更新字段
以下三個類是以原子方式更新字段,
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
- AtomicLongFieldUpdater:原子更新長整型字段的更新器。
- AtomicStampedReference:原子更新帶有版本號的引用類型,用於解決使用CAS進行原子更新時,可能出現的ABA問題。
以AtomicIntegerFieldUpdater為例,
package concurrency; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterTest { private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater .newUpdater(User.class, "old"); public static void main(String[] args) { User conan = new User("conan", 10); System.out.println(a.getAndIncrement(conan)); System.out.println(a.get(conan)); } public static class User { private String name; //注意需要用volatile修飾 public volatile int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
參考資料
《JAVA並發編程實戰》