程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> AtomicInteger源碼分析,atomicinteger源碼

AtomicInteger源碼分析,atomicinteger源碼

編輯:JAVA綜合教程

AtomicInteger源碼分析,atomicinteger源碼


  • 問題背景

  最近在看LinkedBlockingQueue看到了其中的count使用AtomicInteger修飾,之前也看過AtomicInteger的一些解釋,也是似懂非懂的,今天深入的了解了其實現方式,學到了很多東西。

  • 基礎介紹

   要對AtomicInteger有一個深入的認識,就必須要了解一下悲觀鎖和樂觀鎖。cpu是時分復用的,也就是把cpu的時間片,分配給不同的線程/進程輪流執行,時間片與時間片之間,需要進行cpu切換,也就是會發生進程的切換。切換涉及到清空寄存器,緩存數據。然後重新加載新的thread所需數據。當一個線程被掛起時,加入到阻塞隊列,在一定的時間或條件下,在通過notify(),notifyAll()喚醒回來。在某個資源不可用的時候,就將cpu讓出,把當前等待線程切換為阻塞狀態。等到資源(比如一個共享數據)可用了,那麼就將線程喚醒,讓他進入runnable狀態等待cpu調度。這就是典型的悲觀鎖的實現。獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,它假設最壞的情況,並且只有在確保其它線程不會造成干擾的情況下執行,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

  但是,由於在進程掛起和恢復執行過程中存在著很大的開銷。當一個線程正在等待鎖時,它不能做任何事,所以悲觀鎖有很大的缺點。舉個例子,如果一個線程需要某個資源,但是這個資源的占用時間很短,當線程第一次搶占這個資源時,可能這個資源被占用,如果此時掛起這個線程,可能立刻就發現資源可用,然後又需要花費很長的時間重新搶占鎖,時間代價就會非常的高。

   所以就有了樂觀鎖的概念,他的核心思路就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。在上面的例子中,某個線程可以不讓出cpu,而是一直while循環,如果失敗就重試,直到成功為止。所以,當數據爭用不嚴重時,樂觀鎖效果更好。比如我們要說的AtomicInteger底層同步CAS就是一種樂觀鎖思想的應用。

  CAS就是Compare and Swap的意思,比較並操作。很多的cpu直接支持CAS指令。CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個操作數,內存值V,預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什麼都不做。

   在CAS操作中,會出現ABA問題。就是如果V的值先由A變成B,再由B變成A,那麼仍然認為是發生了變化,並需要重新執行算法中的步驟。有簡單的解決方案:不是更新某個引用的值,而是更新兩個值,包括一個引用和一個版本號,即使這個值由A變為B,然後為變為A,版本號也是不同的。

  • AtomicInteger分析

  Atomic包下類的理解:

  Atomic包是Java.util.concurrent下的另一個專門為線程安全設計的Java包,包含多個原子操作類。這個包裡面提供了一組原子變量類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是借助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。可以對基本數據、數組中的基本數據、對類中的基本數據進行操作。原子變量類相當於一種泛化的volatile變量,能夠支持原子的和有條件的讀-改-寫操作。——  引自@chenzehe 的博客。

  先來看一下AtomicInteger中getAndIncrement()方法的實現:

1 public final int getAndIncrement() {
2         for (;;) {
3             int current = get();
4             int next = current + 1;
5             if (compareAndSet(current, next))
6                 return current;
7         }
8 }

  這個方法的做法為先獲取到當前的 value 屬性值,然後將 value 加 1,賦值給一個局部的 next 變量,然而,這兩步都是非線程安全的,但是內部有一個死循環,不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法裡面,compareAndSet()方法的代碼如下:

1 public final boolean compareAndSet(int expect, int update) {
2         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
3 }

  compareAndSet 傳入的為執行方法時獲取到的 value 屬性值,next 為加 1 後的值, compareAndSet所做的為調用 Sun 的 UnSafe 的 compareAndSwapInt 方法來完成,此方法為 native 方法,compareAndSwapInt 基於的是CPU 的 CAS指令來實現的。所以基於 CAS 的操作可認為是無阻塞的,一個線程的失敗或掛起不會引起其它線程也失敗或掛起。並且由於 CAS 操作是 CPU 原語,所以性能比較好。

  下面以具體的例子分析下AtomicInteger的實現:

  計數器(Counter),采用Java裡比較方便的鎖機制synchronized關鍵字,初步的代碼如下:

 1 public class Counter {
 2     private int value;  
 3       
 4     public synchronized int getValue() {  
 5         return value;  
 6     }  
 7   
 8     public synchronized int increment() {  
 9         return ++value;  
10     }  
11   
12     public synchronized int decrement() {  
13         return --value;  
14     }  
15 }

  synchronized關鍵字是基於阻塞的鎖機制,也就是說當一個線程擁有鎖的時候,訪問同一資源的其它線程需要等待,直到該線程釋放鎖,這也就我們前面所說的悲觀鎖。這裡會有些問題:首先,如果被阻塞的線程優先級很高很重要怎麼辦?其次,如果獲得鎖的線程一直不釋放鎖怎麼辦?(這種情況是非常糟糕的)。還有一種情況,如果有大量的線程來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭(事實上CPU的主要工作並非這些),同時,還有可能出現一些例如死鎖之類的情況,最後,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對於像計數器這樣的需求有點兒過於笨重,因此,對於這種需求我們期待一種更合適、更高效的線程安全機制。

  下面我們就以模擬CAS機制來實現Counter的例子:

   CAS類:

 1 public class SimpleCAS {
 2     private volatile int value;
 3     public synchronized int getValue(){
 4         return value;  
 5     } 
 6     public synchronized boolean comperaAndSwap(int expectedValue,int newValue){
 7         int oldValue = value;
 8         if(oldValue == expectedValue){
 9             value = newValue;
10             return true;
11         }else{
12             return false;
13         }
14     }
15 }

  CASCounter類:

 1 public class CASCounter {
 2     private SimpleCAS cas;  
 3     public int getValue(){
 4         return cas.getValue();
 5     }
 6     public int increment(){
 7         int olevalue = cas.getValue();
 8         for (; ;) {
 9             if(cas.comperaAndSwap(olevalue, olevalue+1)){
10                 return cas.getValue();
11             }
12         }
13          
14     }
15 }

  上面的模擬不是CSA的真正實現,其實我們在語言層面是沒有做任何同步的操作的,大家也可以看到源碼沒有任何鎖加在上面,可它為什麼是線程安全的呢?這就是Atomic包下這些類的奧秘:語言層面不做處理,我們將其交給硬件—CPU和內存,利用CPU的多處理能力,實現硬件層面的阻塞,再加上volatile變量的特性即可實現基於原子操作的線程安全。所以說,CAS並不是無阻塞,只是阻塞並非在語言、線程方面,而是在硬件層面,所以無疑這樣的操作會更快更高效!

  總結一下,AtomicInteger基於沖突檢測的樂觀並發策略。 可以想象,這種樂觀在線程數目非常多的情況下,失敗的概率會指數型增加。

  參考內容:http://blog.csdn.net/zhangerqing/article/details/43057799,http://www.mamicode.com/info-detail-862009.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved