Java中每一個對象都有1個內(nèi)置鎖,當程序運行到非靜態(tài)的synchronized同步方法上時,自動取得與正在履行代碼類確當前實例(this實例)有關(guān)的鎖。取得1個對象的鎖也稱為獲得鎖、鎖定對象、在對象上鎖定或在對象上同步。
當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。
1個對象只有1個鎖。所以,如果1個線程取得該鎖,就沒有其他線程可以取得鎖,直到第1個線程釋放(或返回)鎖。這也意味著任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
Java中的同步塊用synchronized標記。同步塊在Java中是同步在某個對象上。所有同步在1個對象上的同步塊在同時只能被1個線程進入并履行操作。所有其他等待進入該同步塊的線程將被阻塞,直到履行該同步塊中的線程退出。
有4種不同的同步塊:
1. 實例方法
2. 靜態(tài)方法
3. 實例方法中的同步塊
4. 靜態(tài)方法中的同步塊
上述同步塊都同步在不同對象上。實際需要那種同步塊視具體情況而定。
實例方法同步
下面是1個同步的實例方法:
public synchronized void add(int value){
this.count += value;
}
注意在方法聲明中同步(synchronized )關(guān)鍵字。這告知Java該方法是同步的。
Java實例方法同步是同步在具有該方法的對象上。這樣,每一個實例其方法同步都同步在不同的對象上,即該方法所屬的實例。只有1個線程能夠在實例方法同步塊中運行。如果有多個實例存在,那末1個線程1次可以在1個實例同步塊中履行操作。1個實例1個線程。
靜態(tài)方法同步
靜態(tài)方法同步和實例方法同步方法1樣,也使用synchronized 關(guān)鍵字。Java靜態(tài)方法同步以下示例:
public static synchronized void add(int value){
count += value;
}
一樣,這里synchronized 關(guān)鍵字告知Java這個方法是同步的。
靜態(tài)方法的同步是指同步在該方法所在的類對象上。由于在Java虛擬機中1個類只能對應(yīng)1個類對象,所以同時只允許1個線程履行同1個類中的靜態(tài)同步方法。
對不同類中的靜態(tài)同步方法,1個線程可以履行每一個類中的靜態(tài)同步方法而無需等待。不管類中的那個靜態(tài)同步方法被調(diào)用,1個類只能由1個線程同時履行。
實例方法中的同步塊
有時你不需要同步全部方法,而是同步方法中的1部份。Java可以對方法的1部份進行同步。
在非同步的Java方法中的同步塊的例子以下所示:
public void add(int value){
synchronized(this){
this.count += value;
}
}
示例使用Java同步塊構(gòu)造器來標記1塊代碼是同步的。該代碼在履行時和同步方法1樣。
注意Java同步塊構(gòu)造器用括號將對象括起來。在上例中,使用了“this”,即為調(diào)用add方法的實例本身。在同步構(gòu)造器中用括號括起來的對象叫做監(jiān)視器對象。上述代碼使用監(jiān)視器對象同步,同步實例方法使用調(diào)用方法本身的實例作為監(jiān)視器對象。
1次只有1個線程能夠在同步于同1個監(jiān)視器對象的Java方法內(nèi)履行。
下面兩個例子都同步他們所調(diào)用的實例對象上,因此他們在同步的履行效果上是等效的。
public class MyClass {
public synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public void log2(String msg1, String msg2){
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
在上例中,每次只有1個線程能夠在兩個同步塊中任意1個方法內(nèi)履行。
如果第2個同步塊不是同步在this實例對象上,那末兩個方法可以被線程同時履行。
靜態(tài)方法中的同步塊
和上面類似,下面是兩個靜態(tài)方法同步的例子。這些方法同步在該方法所屬的類對象上。
public class MyClass {
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
這兩個方法不允許同時被線程訪問。
如果第2個同步塊不是同步在MyClass.class這個對象上。那末這兩個方法可以同時被線程訪問。
使用同步方法1些注意細節(jié):
1、線程同步的目的是為了保護多個線程反問1個資源時對資源的破壞。
2、線程同步方法是通過鎖來實現(xiàn),每一個對象都有切唯一1個鎖,這個鎖與1個特定的對象關(guān)聯(lián),線程1旦獲得了對象鎖,其他訪問該對象的線程就沒法再訪問該對象的其他同步方法。
3、對靜態(tài)同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態(tài)和非靜態(tài)方法的鎖互不干預(yù)。1個線程取得鎖,當在1個同步方法中訪問另外對象上的同步方法時,會獲得這兩個對象鎖。
4、對同步,要時刻蘇醒在哪一個對象上同步,這是關(guān)鍵。
5、編寫線程安全的類,需要時刻注意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,并保證原子操作期間別的線程沒法訪問競爭資源。
6、當多個線程等待1個對象鎖時,沒有獲得到鎖的線程將產(chǎn)生阻塞。
7、死鎖是線程間相互等待鎖鎖釀成的,在實際中產(chǎn)生的幾率非常的小。
在java 5.0之前,在調(diào)和對象同享的訪問時可使用的機制只有synchronized和volatile。java 5.0增加了1種新的機制ReentrantLock。與之條件到過的機制相反,ReentrantLock其實不是1種替換內(nèi)置加鎖的方法,而是當內(nèi)置加鎖機制不適用時,作為1種可選擇的高級功能。
Lock 接口的源碼:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock( long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
與內(nèi)置加鎖機制不同,lock接口提供了1種無條件的、可輪詢的、定時的和可中斷的鎖獲得機制,所有的加鎖和解鎖方法都是顯示的,在Lock的實現(xiàn)中必須提供與內(nèi)部鎖相同的內(nèi)存可見性語義,但在加鎖語義、調(diào)度算法、順序保證和性能特性方面可以有所不同。
ReentrantLock實現(xiàn)了Lock接口,并提供了與synchronized相同的互斥性和內(nèi)存可見性。
public class ReentrantLockextends Objectimplements Lock, Serializable
與synchronized1樣,ReentrantLock還提供了可重入的加鎖語義,與synchronized相比他還為處理鎖機制不可用性問題提供了更高的靈活性
為何要創(chuàng)建1種與內(nèi)置鎖如此相近的新加鎖機制?
在大多數(shù)情況下,內(nèi)置鎖都能夠很好的工作,但在功能上存在1些局限性,例如:沒法提供中斷1個正在等待獲得鎖的線程或沒法在要求獲得1個鎖時無窮等待下去。內(nèi)置鎖必須在獲得該鎖的代碼中釋放,這雖然簡化了編碼工作(還能與異常處理操作很好的實現(xiàn)交互),但卻沒法實現(xiàn)非阻塞結(jié)構(gòu)的加鎖規(guī)則。
使用Lock接口的標準情勢以下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
注意必須在finally中釋放鎖,由于如果在try中出現(xiàn)了異常,并且沒有在finally中進行鎖的釋放,那末該鎖就永久沒法釋放了。還需斟酌在try中拋出異常的情況,如果可能使對象處于某種不1致的狀態(tài),那末就需要更多的try-catch或try-finally代碼快。
可定時的與可輪詢的鎖獲得模式是由tryLock方法實現(xiàn)的,與無條件的鎖獲得模式相比,它具有更完善的毛病恢復(fù)機制。
輪詢鎖:
利用tryLock來獲得兩個鎖,如果不能同時取得,那末回退并重新嘗試。
public boolean transferMoney(Account fromAcct,
Account toAcct,
DollarAmount amount,
long timeout,
TimeUnit unit)
throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModulusNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
}
finally {
toAcct.lock.unlock();
}
}
}
finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
索取鎖的時候可以設(shè)定1個超時時間,如果超過這個時間還沒索取到鎖,則不會繼續(xù)梗塞而是放棄此次任務(wù),示例代碼以下:
public boolean trySendOnSharedLine(String message,
long timeout, TimeUnit unit)
throws InterruptedException {
long nanosToLock = unit.toNanos(timeout)
- estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, NANOSECONDS))
return false;
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
還有可中斷鎖的獲得和非塊結(jié)構(gòu)加鎖、讀寫鎖,加鎖一樣還有性能斟酌因素,和鎖的公平性,和如何選擇ReentrantLock和Synchronized,這些將在下篇博客介紹。