原子類
Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中
的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。
因為變量的類型有很多種,所以在Atomic包里一共提供了13個類,屬于4種類型的原子更
新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。
Atomic包里的類基本都是使用Unsafe實現的包裝類
java.util.concurrent.atomic中的類可以分成4組:
- 標量類(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 復合變量類:AtomicMarkableReference,AtomicStampedReference
CAS
CAS(Compare-And-Swap)算法保證數據操作的原子性。
CAS 算法是硬件對于并發操作共享數據的支持。
CAS 包含了三個操作數:
內存值 V
預估值 A
更新值 B
當且僅當 V == A 時,V 將被賦值為 B,否則循環著不斷進行判斷 V 與 A 是否相等。
CAS的全稱為Compare-And-Swap,是一條CPU的原子指令,其作用是讓CPU比較后原子地更新某個位置的值,經過調查發現,其實現方式是基于硬件平臺的匯編指令,就是說CAS是靠硬件實現的,JVM只是封裝了匯編調用,那些AtomicInteger類便是使用了這些封裝后的接口。
atomic包的原子包下使用這種方案。例如AtomicInteger的getAndIncrement自增+1的方法,經查看源碼如下:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
源碼中for循環體的第一步先取得AtomicInteger里存儲的數值,第二步對AtomicInteger的當前數值進行加1操作,關鍵的第三步調用compareAndSet方法來進行原子更新操作,該方法先檢查當前數值是否等于current,等于意味著AtomicInteger的值沒有被其他線程修改過,則將AtomicInteger的當前數值更新成next的值,如果不等compareAndSet方法會返回false,程序會進入for循環重新進行compareAndSet操作
Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具體實現都是使用了native方法
/**
* 如果當前數值是expected,則原子的將Java變量更新成x
* @return 如果更新成功則返回true
*/
public final native boolean compareAndSwapObject(Object o,
long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);
標量類
- AtomicBoolean:原子更新布爾變量
- AtomicInteger:原子更新整型變量
- AtomicLong:原子更新長整型變量
- AtomicReference:原子更新引用類型
具體到每個類的源代碼中,提供的方法基本相同,這里以AtomicInteger為例進行說明
AtomicInteger源碼主要的方法如下,原理主要都要都是采用了CAS,這里不再累述
public class AtomicInteger extends Number implements java.io.Serializable {
public AtomicInteger(int initialValue) {
value = initialValue;
}
//返回當前的值
public final int get() {
return value;
}
//最終會設置成新值
public final void set(int newValue) {
value = newValue;
}
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//原子更新為新值并返回舊值
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
//如果輸入的值等于預期值,則以原子方式更新為新值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//原子自增
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
//原子方式將當前值與輸入值相加并返回結果
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
}
數組類
- AtomicIntegerArray:原子更新整型數組的某個元素
- AtomicLongArray:原子更新長整型數組的某個元素
- AtomicReferenceArray:原子更新引用類型數組的某個元素
AtomicIntegerArray類主要是提供原子的方式更新數組里的整型,其常用方法如下。
- int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加。
- boolean compareAndSet(int i,int expect,int update):如果當前值等于預期值,則以原子方式將數組位置i的元素設置成update值。
以上幾個類提供的方法幾乎一樣,所以這里以AtomicIntegerArray為例進行講解
public class AtomicIntegerArray implements java.io.Serializable {
private static final long serialVersionUID = 2862133569453604235L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
public AtomicIntegerArray(int length) {
array = new int[length];
}
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
public final void lazySet(int i, int newValue) {
unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
}
public final int getAndSet(int i, int newValue) {
long offset = checkedByteOffset(i);
while (true) {
int current = getRaw(offset);
if (compareAndSetRaw(offset, current, newValue))
return current;
}
}
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(array, offset, expect, update);
}
}
這里關鍵的實現也還是使用了CAS的策略,具體通過unsafe的native方法進行實現
原子更新字段類
如果需原子地更新某個類里的某個字段時,就需要使用原子更新字段類,Atomic包提供
了以下3個類進行原子字段更新。
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新長整型字段的更新器。
- AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起
來,可用于原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的
ABA問題。
要想原子地更新字段類需要兩步。第一步,因為原子更新字段類都是抽象類,每次使用的
時候必須使用靜態方法newUpdater()創建一個更新器,并且需要設置想要更新的類和屬性。第
二步,更新類的字段(屬性)必須使用public volatile修飾符
public class AtomicIntegerFieldUpdaterTest {
// 創建原子更新器,并設置需要更新的對象類和對象的屬性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 設置柯南的年齡是10歲
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;
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;
}
}
}
代碼執行后輸出如下。
10
11
并發工具類
CountDownLatch
CountDownLatch類是一個同步計數器,構造時默認接收一個初始值,每調用一次countDown()方法,計數器減1。計數器>0時,await()方法會阻塞;當計數器=0時會得到await()會立即得到響應
CountDownLatch類位于java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他4個任務執行完畢之后才能執行,此時就可以利用CountDownLatch來實現這種功能了
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2個子線程執行完畢...");
latch.await();
System.out.println("2個子線程已經執行完畢");
System.out.println("繼續執行主線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執行結果:
線程Thread-0正在執行
線程Thread-1正在執行
等待2個子線程執行完畢...
線程Thread-0執行完畢
線程Thread-1執行完畢
2個子線程已經執行完畢
繼續執行主線程
CyclicBarrier
CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,屏障才會
開門,所有被屏障攔截的線程才會繼續運行。當所有等待線程都被釋放以后,CyclicBarrier可以被重用。CyclicBarrier類位于java.util.concurrent包下,CyclicBarrier提供2個構造器:
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
參數parties指讓多少個線程或者任務等待至barrier狀態;參數barrierAction為當這些線程都達到barrier狀態時會執行的內容
public class Test {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
try {
Thread.sleep(5000); //以睡眠來模擬寫入數據操作
System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有線程寫入完畢,繼續處理其他任務...");
}
}
}
執行結果:
線程Thread-0正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
Semaphore
Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源。Semaphore可以控同時訪問的線程個數,通過acquire()獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。
假若一個工廠有5臺機器,但是有8個工人,一臺機器同時只能被一個工人使用,只有使用完了,其他工人才能繼續使用。那么我們就可以通過Semaphore來實現:
public class Test {
public static void main(String[] args) {
int N = 8; //工人數
Semaphore semaphore = new Semaphore(5); //機器數目
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一個機器在生產...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"釋放出機器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執行結果:
工人0占用一個機器在生產...
工人1占用一個機器在生產...
工人2占用一個機器在生產...
工人4占用一個機器在生產...
工人5占用一個機器在生產...
工人0釋放出機器
工人2釋放出機器
工人3占用一個機器在生產...
工人7占用一個機器在生產...
工人4釋放出機器
工人5釋放出機器
工人1釋放出機器
工人6占用一個機器在生產...
工人3釋放出機器
工人7釋放出機器
工人6釋放出機器
Exchanger
Exchanger(交換者)是一個用于線程間協作的工具類。Exchanger用于進行線程間的數據交換。它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數據。這兩個線程通過exchange方法交換數據,如果第一個線程先執行exchange()方法,它會一直等待第二個線程也執行exchange方法,當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。當在運行不對稱的活動時很有用。比如說,一個線程向buffer中填充數據,另一個線程從buffer中消費數據;這些線程可以用Exchange來交換數據。這個交換對于兩個線程來說都是全的
package com.clzhang.sample.thread;
import java.util.*;
import java.util.concurrent.Exchanger;
public class SyncExchanger {
private static final Exchanger exchanger = new Exchanger();
class DataProducer implements Runnable {
private List list = new ArrayList();
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("生產了一個數據,耗時1秒");
list.add(new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
list = (List) exchanger.exchange(list);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
System.out.println("Producer " + iterator.next());
}
}
}
class DataConsumer implements Runnable {
private List list = new ArrayList();
@Override
public void run() {
for (int i = 0; i < 5; i++) {
list.add("這是一個收條。");
}
try {
list = (List) exchanger.exchange(list);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
Date d = (Date) iterator.next();
System.out.println("Consumer: " + d);
}
}
}
public static void main(String[] args) {
SyncExchanger ins = new SyncExchanger();
new Thread(ins.new DataProducer()).start();
new Thread(ins.new DataConsumer()).start();
}
}
執行結果:
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Consumer: Thu Sep 12 17:21:39 CST 2013
Consumer: Thu Sep 12 17:21:40 CST 2013
Consumer: Thu Sep 12 17:21:41 CST 2013
Consumer: Thu Sep 12 17:21:42 CST 2013
Consumer: Thu Sep 12 17:21:43 CST 2013