lpop 原子_【concurrent】面试重灾区之原子操作你有必要了解下
概述
在JDK1.5+的版本中,Doug Lea和他的團隊還為我們提供了一套用于保證線程安全的原子操作。我們都知道在多線程環境下,對于更新對象中的某個屬性、更新基本類型數據、更新數組(集合)都可能產生臟數據問題(如果您不清楚這個問題,請Google或者Baidu。這邊文章本身不討論臟數據產生的具體原因)。
為了避免多線程環境下的臟數據問題,JDK1.5的版本中為我們提供了java.util.concurrent.atomic原子操作包。所謂“原子”操作,是指一組不可分割的操作:操作者對目標對象進行操作時,要么完成所有操作后其他操作者才能操作;要么這個操作者不能進行任何操作。
java.util.concurrent.atomic原子操作包為我們提供了四類原子操作:原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。靈活使用它們完全可以我們在日常工作中遇到的多線程數據臟讀問題。
1.原子操作基本類型
- AtomicBoolean:布爾數據的原子操作
- AtomicInteger:整型數字的原子操作
- AtomicLong:長整型數字的原子操作
這里我們首先使用AtomicInteger給出一段使用代碼,讓各位讀者對基本類型的原子操作有一個感性的認識,然后再給出常用的API方法。基本的使用過程如下:
package test.thread.atomic;import java.util.concurrent.atomic.AtomicInteger;public class TestAtomic { public static void main(String[] args) throws Exception { // 實例化了一個AtomicInteger類的對象atomic并定義初始值為1 AtomicInteger atomic = new AtomicInteger(1); // 進行atomic的原子化操作:增加1并且獲取這個增加后的新值 atomic.incrementAndGet(); }}在以上兩句代碼中,我們看到了原子操作的基本使用。但是有的讀者要問了,這和index++有什么不同嗎?最大的不同是:index++不是線程安全的。本文由于篇幅限制不過多介紹為什么它不是線程安全的。
那么我們重點分析一下AtomicInteger的源代碼,來看一下為什么incrementAndGet()方法是怎么做到原子性的(只列出相關部分的代碼):
public class AtomicInteger extends Number implements java.io.Serializable { ...... // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); ...... private volatile int value; ...... /** * Gets the current value. * * @return the current value */ public final int get() { return value; } ...... /** * Atomically increments by one the current value. * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } ...... /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }}2.悲觀鎖和樂觀鎖
上一小節中給出的AtomicInteger源代碼,有部分讀者是不是感覺有點看不懂?是不是有幾個疑問在您心中:為什么AtomicInteger不使用synchronized關鍵字就可以實現線程安全的原子性操作?為什么incrementAndGet方法中居然還有一個死循環?
要解決這些疑問,我們首先就要介紹樂觀鎖和悲觀鎖以及JAVA對它的支持。悲觀鎖是一種獨占鎖,它假設的前提是“沖突一定會發生”,所以處理某段可能出現數據沖突的代碼時,這個代碼段就要被某個線程獨占。而獨占意味著“其它即將執行這段代碼的其他線程”都將進入“阻塞”/“掛起”狀態。是的,synchronized關鍵字就是java對于悲觀鎖的實現。
由于悲觀鎖的影響下,其他線程都將進入 阻塞/掛起 狀態。而我們在之前的文章中都已經講過,CPU執行線程狀態切換是要耗費相當資源的,這主要涉及到CPU寄存器的操作。所以悲觀鎖在性能上不會有太多驚艷的表現(但是也不至于成為性能瓶頸)
有悲觀鎖的存在當然就有樂觀鎖的存在。樂觀鎖假定“沖突不一定會出現”,如果出現沖突則進行重試,直到沖突消失。 由于樂觀鎖的假定條件,所以樂觀鎖不會獨占資源,性能自然在多數情況下就會好于悲觀鎖。AtomicInteger是一個標準的樂觀鎖實現,sun.misc.Unsafe是JDK提供的樂觀鎖的支持。為什么是多數情況呢?因為一旦多線程對某個資源的搶占頻度達到了某種規模,就會導致樂觀鎖內部出現多次更新失敗的情況,最終造成樂觀鎖內部進入一種“活鎖”狀態。這時樂觀鎖的性能反而沒有悲觀鎖好。
您在incrementAndGet中,會看到有一個“死循環”,這是incrementAndGet方法中有“比較—重試”的需求。現在您明白了悲觀鎖和樂觀鎖的不同,那我們再次審視incrementAndGet方法中的代碼(JDK1.7):
public final int incrementAndGet() { // 一直循環的目的是為了“預期值”與“真實值”不一致的情況下, // 能夠重新進行+1計算 for (;;) { // 取得/重新取得 當前的value值 int current = get(); // 將當前值+1 int next = current + 1; // 這是最關鍵的,使用JDK中實現的CAS機制 // 對當前值和預期值進行比較 // 如果當前值和預期的不一樣,說明有某一個其他線程完成了值的更改 // 那么進行下一次循環,進行重新操作(因為之前的操作結果就不對了) if (compareAndSet(current, next)) return next; }}這就是整個利用樂觀鎖進行原子操作的過程。當然您在理解了這個過程后,就可以將樂觀鎖的支持直接運用到您的業務代碼中,幫助改善性能了。祝賀您!
在代碼中還有一個volatile關鍵字,volatile關鍵字用于修飾變量,線程在每次使用該變量時,都會讀取變量修改后的最的值。注意,如果只是使用volatile,也不足以保證數據操作的原子性。
3.原子操作數組
- AtomicIntegerArray:原子操作整型數組
- AtomicLongArray:原子操作長整型數組
- AtomicReferenceArray:原子操作對象引用數組(后文會介紹對象引用的原子操作)
我們首先來看一看AtomicIntegerArray的基本使用。代碼如下所示:
package test.thread.atomic;import java.util.concurrent.atomic.AtomicIntegerArray;public class TestAtomicArray { public static void main(String[] args) throws Exception { AtomicIntegerArray atomicArray = new AtomicIntegerArray(5); // 設置指定索引位的數值 atomicArray.set(0, 5); // 您也可以通過以下方法設置 //(實際上默認值為0,這里加了5) // atomicArray.addAndGet(0, 5); // -- int current = atomicArray.decrementAndGet(0); System.out.println("current = " + current); }}在代碼中,我們使用addAndGet方法設置數字指定索引位的值;使用decrementAndGet方法將指定索引位的值減少1,并且取得最新值。
public class AtomicIntegerArray implements java.io.Serializable { ...... private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(int[].class); ...... private final int[] array; ...... /** * Creates a new AtomicIntegerArray of the given length, with all * elements initially zero. * * @param length the length of the array */ public AtomicIntegerArray(int length) { array = new int[length]; } ...... /** * Atomically decrements by one the element at index {@code i}. * * @param i the index * @return the updated value */ public final int decrementAndGet(int i) { return addAndGet(i, -1); } ...... public final int addAndGet(int i, int delta) { long offset = checkedByteOffset(i); while (true) { int current = getRaw(offset); int next = current + delta; if (compareAndSetRaw(offset, current, next)) return next; } }}如果您想查看AtomicIntegerArray中的完整操作方式,可以查看JDK的API文檔,這里的文章只給出一些主要的操作方式,以便您進行查看:
get(int i):獲取數組指定位置的值,并不會改變原來的值。
set(int i, int newValue):為數組指定索引位設置一個新值。數組的索引位都是從0開始計數。
getAndSet(int i, int newValue):獲取數組指定位置的原始值后,用newValue這個新值進行覆蓋。
getAndAdd(int i, int delta):獲取數組指定索引位的原始值后,為數組指定索引位的值增加delta。那么還有個類似的操作為:addAndGet。
incrementAndGet(int i):為數組指定索引位的值+1后,然后獲取這個位置上的新值。當然,還有個類似的操作:getAndIncrement。
decrementAndGet(int i):為數組指定索引位的值-1后,然后獲取這個位置上的新值。當然,類似的操作為:getAndDecrement。
和上文中我們介紹的AtomicInteger類相似,AtomicIntegerArray中的decrementAndGet方法(還有其他操作方法)也是樂觀鎖的一個應用。
實際上不僅如此,在JDK1.5+中,Doug Lea和他的團隊為我們提供的線程安全的數據操作,基本上都是基于樂觀鎖的實現。包括(但不限于):java.util.concurrent.atomic包中的原子數據操作、java.util.concurrent包中的線程安全的數據結構等等。
4.原子操作對象字段
- AtomicIntegerFieldUpdater:整型數據字段更新器
- AtomicLongFieldUpdater:長型數據字段更新器
- AtomicReferenceFieldUpdater:對象數據字段更新器
- AtomicReference:對象原子操作
java.util.concurrent.atomic還為我們提供了進行對象(和對象中依賴)原子操作的方式。當然,同樣也似基于樂觀鎖。為了演示這樣的操作,我們首先要定義一個被操作的類,以便稍后對它進行實例化。
在這個示例程序中,我們定義了一個“學生”類:Student,并且為這個Student引入了一個“成績”類:Performance。我們先來看看這兩個類的定義:
/** * 代表學生的Student類 * @author yinwenjie */class Student { /** * 學生成績 */ private Performance performance; /** * 學生姓名 */ private String name; public Student(String name , Integer performance) { this.name = name; this.performance = new Performance(); this.performance.setPerformance(performance); } /** * @return the performance */ public Performance getPerformance() { return performance; } /** * @param performance the performance to set */ public void setPerformance(Performance performance) { this.performance = performance; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; }}/** * 代表著學生成績 * @author yinwenjie */class Performance { /** * 成績屬性是一個整數 */ private Integer performance; /** * @return the performance */ public Integer getPerformance() { return performance; } /** * @param performance the performance to set */ public void setPerformance(Integer performance) { this.performance = performance; }}好了,我們來看看原子操作包是如何幫助我們進行多線程安全的對象(和對象引用)操作的。
首先我們實例化這個Student對象,然后使用AtomicReference對這個對象進行操作:
public static void main(String[] args) throws RuntimeException { Student student = new Student("yinwenjie" , 80); AtomicReference ref = new AtomicReference(student); student = new Student("yinwenjie" , 70); Student oldStudent = ref.getAndSet(student); System.out.println(student + "和" + oldStudent + "是兩個對象"); System.out.println("AtomicReference保證了賦值時的原子操作性");}可以看出,我們使用AtomicReference對某一個對象的賦值過程進行了操作。但是很明顯,這絕對不是我們的目的。我們的目的是,保證student對象不變,只是改變student的成績屬性。所以,我們應當使用AtomicReferenceFieldUpdater。
public class Student { ...... /** * 學生成績 */ private volatile Performance performance; /** * 學生成績“更改者” */ // 會重點講解關于“更改器”的參數問題 private AtomicReferenceFieldUpdater performance_updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, Performance.class, "performance"); ...... /** * @return the performance */ public Performance getPerformance() { return performance; } /** * @param performance the performance to set */ public void setPerformance(Performance performance) { // 注意,這里設置的是updater,而不是直接設置performance屬性 performance_updater.set(this, performance); } ......}AtomicReferenceFieldUpdater.newUpdater這個靜態方法是為了創建一個新的“更新器”。其中的三個參數分別表示:持有要進行原子操作屬性的類、要進行原子操作的類和要進行原子操作的屬性的名稱。
對于Student類來說,持有要進行原子操作屬性的類當然就是Student類本身;要進行原子操作的類當然就是Performance類;屬性名稱我們設置的名稱是“performance”。
另外需要注意的是setPerformance方法。在這個方法中,我們不再直接設置performance參數,而是使用updater的set方法間接設置performance參數。
下面,我們就來測試一下更改后的Student類的使用效果。首先看一下測試代碼:
public static void main(String[] args) throws RuntimeException { Student student = new Student(); Performance newPerformance = new Performance(); newPerformance.setPerformance(80); // 注意,這樣student中的performance屬性 // 就是用了樂觀機制,保證了操作的線程安全性 student.setPerformance(newPerformance); // 再設置一次 Performance otherPerformance = new Performance(); otherPerformance.setPerformance(100); student.setPerformance(otherPerformance); System.out.println("student還是一個"); System.out.println(newPerformance + "和" + otherPerformance + "不一樣了");}以下是運行效果:
第一次student.setPerformance的執行效果:
第二次student.setPerformance的執行效果:
總結
以上是生活随笔為你收集整理的lpop 原子_【concurrent】面试重灾区之原子操作你有必要了解下的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果6可以分屏吗_苹果减肥法可以吃鸡蛋吗
- 下一篇: eclipse javascript_原