前面的章節主要談談原子操作,至于與原子操作一些相關的問題或者說陷阱就放到最后的總結篇來整體說明。從這一章開始花少量的篇幅談談鎖機制。
上一個章節中談到了鎖機制,并且針對于原子操作談了一些相關的概念和設計思想。接下來的文章中,盡可能的深入研究鎖機制,并且理解里面的原理和實際應用場合。
盡管synchronized在語法上已經足夠簡單了,在JDK 5之前只能借助此實現,但是由于是獨占鎖,性能卻不高,因此JDK 5以后就開始借助于JNI來完成更高級的鎖實現。
JDK 5中的鎖是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一對可供讀寫并發的鎖。根據前面的規則,我們從java.util.concurrent.locks.Lock的API開始。
void lock();
獲取鎖。
如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態。
void lockInterruptibly() throws InterruptedException;
如果當前線程未被中斷,則獲取鎖。
如果鎖可用,則獲取鎖,并立即返回。
如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下兩種情況之一以前,該線程將一直處于休眠狀態:
- 鎖由當前線程獲得;或者
- 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷。
如果當前線程:
- 在進入此方法時已經設置了該線程的中斷狀態;或者
- 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
則將拋出
InterruptedException
,并清除當前線程的已中斷狀態。
Condition newCondition();
返回綁定到此 Lock
實例的新 Condition
實例。下一小節中會重點談Condition,此處不做過多的介紹。
boolean tryLock();
僅在調用時鎖為空閑狀態才獲取該鎖。
如果鎖可用,則獲取鎖,并立即返回值 true
。如果鎖不可用,則此方法將立即返回值 false
。
通常對于那些不是必須獲取鎖的操作可能有用。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
如果鎖在給定的等待時間內空閑,并且當前線程未被中斷,則獲取鎖。
如果鎖可用,則此方法將立即返回值 true
。如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下三種情況之一前,該線程將一直處于休眠狀態:
- 鎖由當前線程獲得;或者
- 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷;或者
- 已超過指定的等待時間
如果獲得了鎖,則返回值 true
。
如果當前線程:
- 在進入此方法時已經設置了該線程的中斷狀態;或者
- 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
則將拋出
InterruptedException
,并會清除當前線程的已中斷狀態。
如果超過了指定的等待時間,則將返回值 false
。如果 time 小于等于 0,該方法將完全不等待。
void unlock();
釋放鎖。對應于lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的話應該對應著一個unlock(),這樣可以避免死鎖或者資源浪費。
相對于比較空洞的API,來看一個實際的例子。下面的代碼實現了一個類似于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是線程安全的,此結構中大量使用了Lock對象的lock/unlock方法對。同樣可以看到的是對于自增和自減操作使用了++/--。之所以能夠保證線程安全,是因為Lock對象的lock()方法保證了只有一個線程能夠只有此鎖。需要說明的是對于任何一個lock()方法,都需要一個unlock()方法與之對于,通常情況下為了保證unlock方法總是能夠得到執行,unlock方法被置于finally塊中。另外這里使用了java.util.concurrent.locks.ReentrantLock.ReentrantLock對象,下一個小節中會具體描述此類作為Lock的唯一實現是如何設計和實現的。
盡管synchronized實現Lock的相同語義,并且在語法上比Lock要簡單多,但是前者卻比后者的開銷要大得多。做一個簡單的測試。
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;
在這個例子中每次啟動10個線程,每個線程計算100000次自增操作,重復測試10次,下面是某此測試的結果:
cost1: 624071136
cost2: 2057847833
盡管上面的例子不是非常正式的測試案例,但上面的例子在于說明,Lock的性能比synchronized的要好得多。如果可以的話總是使用Lock替代synchronized是一個明智的選擇。
©2009-2014 IMXYLZ
|求賢若渴