前面的章節(jié)主要談?wù)勗硬僮鳎劣谂c原子操作一些相關(guān)的問(wèn)題或者說(shuō)陷阱就放到最后的總結(jié)篇來(lái)整體說(shuō)明。從這一章開(kāi)始花少量的篇幅談?wù)勬i機(jī)制。
上一個(gè)章節(jié)中談到了鎖機(jī)制,并且針對(duì)于原子操作談了一些相關(guān)的概念和設(shè)計(jì)思想。接下來(lái)的文章中,盡可能的深入研究鎖機(jī)制,并且理解里面的原理和實(shí)際應(yīng)用場(chǎng)合。
盡管synchronized在語(yǔ)法上已經(jīng)足夠簡(jiǎn)單了,在JDK 5之前只能借助此實(shí)現(xiàn),但是由于是獨(dú)占鎖,性能卻不高,因此JDK 5以后就開(kāi)始借助于JNI來(lái)完成更高級(jí)的鎖實(shí)現(xiàn)。
JDK 5中的鎖是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一對(duì)可供讀寫并發(fā)的鎖。根據(jù)前面的規(guī)則,我們從java.util.concurrent.locks.Lock的API開(kāi)始。
void lock();
獲取鎖。
如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態(tài)。
void lockInterruptibly() throws InterruptedException;
如果當(dāng)前線程未被中斷,則獲取鎖。
如果鎖可用,則獲取鎖,并立即返回。
如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在發(fā)生以下兩種情況之一以前,該線程將一直處于休眠狀態(tài):
- 鎖由當(dāng)前線程獲得;或者
- 其他某個(gè)線程中斷當(dāng)前線程,并且支持對(duì)鎖獲取的中斷。
如果當(dāng)前線程:
- 在進(jìn)入此方法時(shí)已經(jīng)設(shè)置了該線程的中斷狀態(tài);或者
- 在獲取鎖時(shí)被中斷,并且支持對(duì)鎖獲取的中斷,
則將拋出
InterruptedException
,并清除當(dāng)前線程的已中斷狀態(tài)。
Condition newCondition();
返回綁定到此 Lock
實(shí)例的新 Condition
實(shí)例。下一小節(jié)中會(huì)重點(diǎn)談Condition,此處不做過(guò)多的介紹。
boolean tryLock();
僅在調(diào)用時(shí)鎖為空閑狀態(tài)才獲取該鎖。
如果鎖可用,則獲取鎖,并立即返回值 true
。如果鎖不可用,則此方法將立即返回值 false
。
通常對(duì)于那些不是必須獲取鎖的操作可能有用。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
如果鎖在給定的等待時(shí)間內(nèi)空閑,并且當(dāng)前線程未被中斷,則獲取鎖。
如果鎖可用,則此方法將立即返回值 true
。如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在發(fā)生以下三種情況之一前,該線程將一直處于休眠狀態(tài):
- 鎖由當(dāng)前線程獲得;或者
- 其他某個(gè)線程中斷當(dāng)前線程,并且支持對(duì)鎖獲取的中斷;或者
- 已超過(guò)指定的等待時(shí)間
如果獲得了鎖,則返回值 true
。
如果當(dāng)前線程:
- 在進(jìn)入此方法時(shí)已經(jīng)設(shè)置了該線程的中斷狀態(tài);或者
- 在獲取鎖時(shí)被中斷,并且支持對(duì)鎖獲取的中斷,
則將拋出
InterruptedException
,并會(huì)清除當(dāng)前線程的已中斷狀態(tài)。
如果超過(guò)了指定的等待時(shí)間,則將返回值 false
。如果 time 小于等于 0,該方法將完全不等待。
void unlock();
釋放鎖。對(duì)應(yīng)于lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的話應(yīng)該對(duì)應(yīng)著一個(gè)unlock(),這樣可以避免死鎖或者資源浪費(fèi)。
相對(duì)于比較空洞的API,來(lái)看一個(gè)實(shí)際的例子。下面的代碼實(shí)現(xiàn)了一個(gè)類似于AtomicInteger的操作。
package xylz.study.concurrency.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AtomicIntegerWithLock {
private int value;
private Lock lock = new ReentrantLock();
public AtomicIntegerWithLock() {
super();
}
public AtomicIntegerWithLock(int value) {
this.value = value;
}
public final int get() {
lock.lock();
try {
return value;
} finally {
lock.unlock();
}
}
public final void set(int newValue) {
lock.lock();
try {
value = newValue;
} finally {
lock.unlock();
}
}
public final int getAndSet(int newValue) {
lock.lock();
try {
int ret = value;
value = newValue;
return ret;
} finally {
lock.unlock();
}
}
public final boolean compareAndSet(int expect, int update) {
lock.lock();
try {
if (value == expect) {
value = update;
return true;
}
return false;
} finally {
lock.unlock();
}
}
public final int getAndIncrement() {
lock.lock();
try {
return value++;
} finally {
lock.unlock();
}
}
public final int getAndDecrement() {
lock.lock();
try {
return value--;
} finally {
lock.unlock();
}
}
public final int incrementAndGet() {
lock.lock();
try {
return ++value;
} finally {
lock.unlock();
}
}
public final int decrementAndGet() {
lock.lock();
try {
return --value;
} finally {
lock.unlock();
}
}
public String toString() {
return Integer.toString(get());
}
}
類AtomicIntegerWithLock是線程安全的,此結(jié)構(gòu)中大量使用了Lock對(duì)象的lock/unlock方法對(duì)。同樣可以看到的是對(duì)于自增和自減操作使用了++/--。之所以能夠保證線程安全,是因?yàn)長(zhǎng)ock對(duì)象的lock()方法保證了只有一個(gè)線程能夠只有此鎖。需要說(shuō)明的是對(duì)于任何一個(gè)lock()方法,都需要一個(gè)unlock()方法與之對(duì)于,通常情況下為了保證unlock方法總是能夠得到執(zhí)行,unlock方法被置于finally塊中。另外這里使用了java.util.concurrent.locks.ReentrantLock.ReentrantLock對(duì)象,下一個(gè)小節(jié)中會(huì)具體描述此類作為L(zhǎng)ock的唯一實(shí)現(xiàn)是如何設(shè)計(jì)和實(shí)現(xiàn)的。
盡管synchronized實(shí)現(xiàn)Lock的相同語(yǔ)義,并且在語(yǔ)法上比Lock要簡(jiǎn)單多,但是前者卻比后者的開(kāi)銷要大得多。做一個(gè)簡(jiǎn)單的測(cè)試。
public static void main(String[] args) throws Exception{
final int max = 10;
final int loopCount = 100000;
long costTime = 0;
for (int m = 0; m < max; m++) {
long start1 = System.nanoTime();
final AtomicIntegerWithLock value1 = new AtomicIntegerWithLock(0);
Thread[] ts = new Thread[max];
for(int i=0;i<max;i++) {
ts[i] = new Thread() {
public void run() {
for (int i = 0; i < loopCount; i++) {
value1.incrementAndGet();
}
}
};
}
for(Thread t:ts) {
t.start();
}
for(Thread t:ts) {
t.join();
}
long end1 = System.nanoTime();
costTime += (end1-start1);
}
System.out.println("cost1: " + (costTime));
//
System.out.println();
costTime = 0;
//
final Object lock = new Object();
for (int m = 0; m < max; m++) {
staticValue=0;
long start1 = System.nanoTime();
Thread[] ts = new Thread[max];
for(int i=0;i<max;i++) {
ts[i] = new Thread() {
public void run() {
for (int i = 0; i < loopCount; i++) {
synchronized(lock) {
++staticValue;
}
}
}
};
}
for(Thread t:ts) {
t.start();
}
for(Thread t:ts) {
t.join();
}
long end1 = System.nanoTime();
costTime += (end1-start1);
}
//
System.out.println("cost2: " + (costTime));
}
static int staticValue = 0;
在這個(gè)例子中每次啟動(dòng)10個(gè)線程,每個(gè)線程計(jì)算100000次自增操作,重復(fù)測(cè)試10次,下面是某此測(cè)試的結(jié)果:
cost1: 624071136
cost2: 2057847833
盡管上面的例子不是非常正式的測(cè)試案例,但上面的例子在于說(shuō)明,Lock的性能比synchronized的要好得多。如果可以的話總是使用Lock替代synchronized是一個(gè)明智的選擇。
©2009-2014 IMXYLZ
|求賢若渴