在簡單介紹java.util.concurrent.atomic包之前,有個概念要先抄襲熟悉一遍:CAS(比較並交換)。現在大多數的處理器都提供對並發 訪問的支持,這個支持的反映方式就是提供硬件的指令支持多處理的特殊需求。比如檢測或者阻止其它處理器的並發訪問來更新共享變量的 指令。對於 Intel x86架構的處理器來說就是通過提供實現CAS或者比較並設置的硬件原語指令集。CAS操作的三個操作數:內存位置(V) ,預期原值(A)和新值(B)。執行的過程通常是:預測內存地址V應該包含值A,如果包含則將值B替換到位置V;否則,不更改任何值,告 知地址V的當前值。CAS對待“讀-修改-寫”的操作一般是檢測這個過程是否有其它的線程在修改變量,如果有那麼這次的CAS操作失敗, 可以嘗試重新進行CAS。講到這裡似乎感覺比 Synchronized還復雜,是否意味著成本不小呢?答案是否。因為它是硬件原生實現的,極為輕 量級的無鎖同步方式。就好像高清解碼一樣,GPU原生硬件解碼比軟解的CPU占用優勢那是相當的不一樣啊!
說到硬件我就想到最近狂能爭論的使用64位操作系統的優勢。現在處理器多數支持64位,意味著處理器的寄存器映射到內存的尋址空間 大大的大了,操作系統 64位的架構或許在內存管理上的挑戰更大了,沒有好的內存壓縮技術,大內存只能是大浪費。同時還表示如果三方 軟件開發者對64位系統內存管理不熟悉,軟件變垃圾的概率變大了。沒有好的64位三方軟件的繁榮,操作系統僅僅作為一個支撐軟件運行的 平台能干什麼呢?所以使用優勢不在操作系統本身而在於平台之上的軟件。又扯遠了,哎...
JDK5以後在java.util.concurrent.atomic包下提供了十幾個原子類。常見的是 AtomicInteger,AtomicLong,AtomicReference以及它們 的數組形式,還有AtomicBoolean和為了處理 ABA問題引入的AtomicStampedReference類,最後就是基於反射的對volatile變量進行更新的 實用工具類:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater。這些原子類理論上能夠大幅的提升性 能。並且java.util.concurrent內的並發集合,線程池,執行器,同步器的內部實現大量的依賴這些無鎖原子類,從而爭取性能的最大化。 下面通過一個簡單的例子看看:
Java代碼
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicInteger;
/**
* User: yanxuxin
* Date: Dec 16, 2009
* Time: 10:49:40 PM
*/
public class AtomicCounterSample extends Thread {
private AtomicCounter atomicCounter;
public AtomicCounterSample(AtomicCounter atomicCounter) {
this.atomicCounter = atomicCounter;
}
@Override
public void run() {
long sleepTime = (long) (Math.random() * 100);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicCounter.counterIncrement();
}
public static void main(String[] args) throws Exception {
AtomicCounter atomicCounter = new AtomicCounter();
for (int i = 0; i < 5000; i++) {
new AtomicCounterSample(atomicCounter).start();
}
Thread.sleep(3000);
System.out.println("counter=" + atomicCounter.getCounter());
}
}
class AtomicCounter {
private AtomicInteger counter = new AtomicInteger(0);
public int getCounter() {
return counter.get();
}
public void counterIncrement() {
for (; ;) {
int current = counter.get();
int next = current + 1;
if (counter.compareAndSet(current, next))
return;
}
}
}
class AtomicCounter2 {
private volatile int counter;
private static final AtomicIntegerFieldUpdater<AtomicCounter2> counterUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicCounter2.class, "counter");
public int getCounter() {
return counter;
}
public int counterIncrement() {
// return counter++;
return counterUpdater.getAndIncrement(this);
}
}
這個例子實現了原子計數器的兩個版本:AtomicCounter,AtomicCounter2。AtomicCounterSample作為 Thread的子類對共享變量 AtomicCounter或者AtomicCounter2內的counter變量進行增幅為1的遞增。主函數的過程是開啟5000線程,並且每個線程隨機睡眠極短時間 後執行遞增。所以線程安全的執行結果應該是5000。
首先看版本1:AtomicCounter內的共享變量使用了Integer的原子類代替,在get()方法中不使用鎖,也不用擔心獲取的過程中別的線程 去改變counter的值,因為這些原子類可以看成volatile的范化擴展,可見性能夠保證。而在counterIncrement()方法中揭示了使用原子類 的重要技巧:循環結合CAS。這個技巧可以幫助我們實現復雜的非阻塞並發集合。方法中的 counter.compareAndSet(current, next)就是原 子類使用的精髓--CAS操作。compareAndSet(...)可以說是原子類搭積木的原材料,在循環中使用它可以讓我們的並發程序昂首挺胸。
再看版本2:AtomicCounter2內有個volatile的共享變量counter,並且有個類變量counterUpdater作為 counter的更新器。在 counterIncrement()裡注釋掉的代碼是非線程安全的。而 counterUpdater.getAndIncrement(this)的內部實現其實和版本1的幾乎一樣。唯 一不同的是通過反射找到要原子操作更新的變量counter,但是“循環+CAS”的精髓是一樣的。
最後看看結果吧:版本1和版本2的無鎖同步的執行分別20次均是5000,正確。版本2把無鎖同步的代碼注釋,把已注釋的非線程安全的代 碼還原執行,平均每10次大概有1~2次出現<5000的數字。這個例子側面證明了++的原子性操作非線程安全是保證不了的。因為“讀-修 改-寫”的操作碰到如下場景:線程A“讀-修改”後“寫”之前,線程B完成“讀-修改-寫”。這時候A,B的寫值是重復的,這就造成了 結果<5000,又杯具了...
多線程的基礎總結到這兒也算給自己一個交代了(可能還有lock),在嘗試用文字和代碼解釋的過程是一個可以獲得更深體會的機會。在 寫這個系列總結的 blog時,通常為了用簡單的例子解釋一個簡單的內容要理解的更加的透徹深入(理解的有可能不對)。所以越想寫的簡單 ,想的就越多,這算是一個可以分享的體會吧。對於並發的集合和執行器,線程池的知識整理雖然有個基本的概念,但是這一塊畢竟還是以 性能說話,所以暫時不知從何說起了,呵呵。