Java併發基礎10:原子性操作類的使用
在 java5 以後,我們接觸到了執行緒原子性操作,也就是在修改時我們只需要保證它的那個瞬間是安全的即可,經過相應的包裝後可以再處理物件的併發修改,本文總結一下Atomic系列的類的使用方法,其中包含:
型別 | Integer | Long | |
---|---|---|---|
基本型別 | AtomicInteger | AtomicLong | AtomicBoolean |
陣列型別 | AtomicIntegerArray | AtomicLongArray | AtomicReferenceArray |
屬性原子修改器 | AtomicIntegerFieldUpdater | AtomicLongFieldUpdater | AtomicReferenceFieldUpdater |
1. 基本型別的使用
首先看一下AtomicInteger的使用,AtomicInteger主要是針對整數的修改的,看一下示例程式碼:
public class AtomicIntegerDemo { /** * 常見的方法列表 * @see AtomicInteger#get()直接返回值 * @see AtomicInteger#getAndAdd(int)增加指定的資料,返回變化前的資料 * @see AtomicInteger#getAndDecrement() 減少1,返回減少前的資料 * @see AtomicInteger#getAndIncrement() 增加1,返回增加前的資料 * @see AtomicInteger#getAndSet(int)設定指定的資料,返回設定前的資料 * * @see AtomicInteger#addAndGet(int)增加指定的資料後返回增加後的資料 * @see AtomicInteger#decrementAndGet() 減少1,返回減少後的值 * @see AtomicInteger#incrementAndGet() 增加1,返回增加後的值 * @see AtomicInteger#lazySet(int)僅僅當get時才會set * * @see AtomicInteger#compareAndSet(int, int) 嘗試新增後對比,若增加成功則返回true否則返回false */ public final static AtomicInteger TEST_INTEGER = new AtomicInteger(1); public static void main(String []args) { for(int i = 0 ; i < 10 ; i++) { //開啟10個執行緒 new Thread() { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int now = TEST_INTEGER.incrementAndGet(); //自增 System.out.println(Thread.currentThread().getName() + " get value : " + now); } }.start(); } } }
看一下結果:
Thread-3 get value : 4Thread-7 get value : 5Thread-9 get value : 9Thread-4 get value : 6Thread-0 get value : 3Thread-1 get value : 8Thread-5 get value : 11Thread-8 get value : 7Thread-2 get value : 10Thread-6 get value : 2
可以看出,10個執行緒之間是執行緒安全的,並沒有衝突。也就是說,我們使用原子性操作類去操作基本型別int就可以解決執行緒安全問題,一個執行緒在操作的時候,會對其它執行緒進行排斥,不用我們手動去使用synchronized實現互斥操作了。AtomicLong和AtomicBoolean類似,就不舉例子了。
2. 陣列型別的使用
下面要開始說Atomic的陣列用法,Atomic的陣列要求不允許修改長度等,不像集合類那麼豐富的操作,不過它可以讓陣列上每個元素的操作絕對安全的,也就是它細化的力度還是到陣列上的元素,做了二次包裝,雖然是陣列型別的,但是最後還是運算元組中存的數,所以會了上面的基本型別的話,陣列型別也很好理解。這裡主要看一下AtomicIntegerArray的使用,其它的類似。
public class AtomicIntegerArrayTest { /** * 常見的方法列表 * @see AtomicIntegerArray#addAndGet(int, int) 執行加法,第一個引數為陣列的下標,第二個引數為增加的數量,返回增加後的結果 * @see AtomicIntegerArray#compareAndSet(int, int, int) 對比修改,引數1:陣列下標,引數2:原始值,引數3,修改目標值,修改成功返回true否則false * @see AtomicIntegerArray#decrementAndGet(int) 引數為陣列下標,將陣列對應數字減少1,返回減少後的資料 * @see AtomicIntegerArray#incrementAndGet(int) 引數為陣列下標,將陣列對應數字增加1,返回增加後的資料 * * @see AtomicIntegerArray#getAndAdd(int, int) 和addAndGet類似,區別是返回值是變化前的資料 * @see AtomicIntegerArray#getAndDecrement(int) 和decrementAndGet類似,區別是返回變化前的資料 * @see AtomicIntegerArray#getAndIncrement(int) 和incrementAndGet類似,區別是返回變化前的資料 * @see AtomicIntegerArray#getAndSet(int, int) 將對應下標的數字設定為指定值,第二個引數為設定的值,返回是變化前的資料 */ private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10}); public static void main(String []args) throws InterruptedException { Thread []threads = new Thread[10]; for(int i = 0 ; i < 10 ; i++) { final int index = i; final int threadNum = i; threads[i] = new Thread() { public void run() { int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1); System.out.println("執行緒編號為:" + threadNum + " , 對應的原始值為:" + (index + 1) + ",增加後的結果為:" + result); } }; threads[i].start(); } for(Thread thread : threads) { thread.join(); } System.out.println("=========================>\n執行已經完成,結果列表:"); for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) { System.out.println(ATOMIC_INTEGER_ARRAY.get(i)); } } }
執行結果是給每個陣列元素加上相同的值,它們之間互不影響。
3. 作為類屬性的使用
當某個資料型別是某個類中的一個屬性的時候,然後我們要操作該資料,就需要使用屬性原子修改器了,這裡還是以Integer為例,即:AtomicIntegerFieldUpdater。示例程式碼如下:
public class AtomicIntegerFieldUpdaterTest { static class A { volatile int intValue = 100; } /** * 可以直接訪問對應的變數,進行修改和處理 * 條件:要在可訪問的區域內,如果是private或挎包訪問default型別以及非父親類的protected均無法訪問到 * 其次訪問物件不能是static型別的變數(因為在計算屬性的偏移量的時候無法計算),也不能是final型別的變數(因為根本無法修改),必須是普通的成員變數 * * 方法(說明上和AtomicInteger幾乎一致,唯一的區別是第一個引數需要傳入物件的引用) * @see AtomicIntegerFieldUpdater#addAndGet(Object, int) * @see AtomicIntegerFieldUpdater#compareAndSet(Object, int, int) * @see AtomicIntegerFieldUpdater#decrementAndGet(Object) * @see AtomicIntegerFieldUpdater#incrementAndGet(Object) * * @see AtomicIntegerFieldUpdater#getAndAdd(Object, int) * @see AtomicIntegerFieldUpdater#getAndDecrement(Object) * @see AtomicIntegerFieldUpdater#getAndIncrement(Object) * @see AtomicIntegerFieldUpdater#getAndSet(Object, int) */ public final static AtomicIntegerFieldUpdater<A> ATOMIC_INTEGER_UPDATER = AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue"); public static void main(String []args) { final A a = new A(); for(int i = 0 ; i < 10 ; i++) { new Thread() { public void run() { if(ATOMIC_INTEGER_UPDATER.compareAndSet(a, 100, 120)) { System.out.println(Thread.currentThread().getName() + " 對對應的值做了修改!"); } } }.start(); } } }
可以看到,這裡需要將類和類屬性傳進去才行,傳進去後其實跟前面操作Integer沒什麼不同了,本質都一樣的,執行一下,結果只有一個執行緒能對其進行修改。
執行緒的原子性操作類的使用就簡單總結到這,其他的操作類原理都相似,可以參考 JDK 的文件,可以很容易寫出相應的測試程式碼。
原子性操作類的使用就分享這麼多,如有錯誤之處,歡迎指正,我們一同進步~