大多數(shù)應用程序要求線程之間互相通信來同步它們的動作。在Java程序中最簡單實現(xiàn)同步的方法就是上鎖。為了防止同時訪問共享資源,線程在使用資源的前后可以給該資源上鎖和開鎖。
給共享變量上鎖就使得Java線程能夠快速方便地通信和同步。某個線程若給一個對象上了鎖,就可以知道沒有其他線程能夠訪問該對象。即使在搶占式模型中,其他線程也不能夠訪問此對象,直到上鎖的線程被喚醒、完成工作并開鎖。那些試圖訪問一個上鎖對象的線程通常會進入睡眠狀態(tài),直到上鎖的線程開鎖。一旦鎖被打開,這些睡眠進程就會被喚醒并移到準備就緒隊列中。
每一個對象都有一個監(jiān)視器(monitor),同時只允許一個線程持有監(jiān)視器從而進行對對象的訪問,那些沒有獲得監(jiān)視器的線程必須等待直到持有監(jiān)視器的線程釋放監(jiān)視器。對象通過synchronized關(guān)鍵字來聲明線程必須獲得監(jiān)視器才能進行對自己的訪問。
在對象級使用鎖通常是一種比較粗糙的方法。為什么要將整個對象都上鎖,而不允許其他線程短暫地使用對象中其他同步方法來訪問共享資源?如果一個對象擁有多個資源,就不需要只為了讓一個線程使用其中一部分資源,就將所有線程都鎖在外面。
class DoubleObjectLock {
TestClassx, y;
Object first_lock = new Object(),second_ylock = new Object();
public void test() {
synchronized(first_lock) {
//first_lock control block
}
synchronized(second_lock) {
// second _lock control block
}
}
public void together() {
synchronized(this) {
//class_lock control block
}
}
}
如果是有條件的同步,可以采用wait/notify/notifyAll。獲得對象監(jiān)視器的線程可以通過調(diào)用該對象的wait方法主動釋放監(jiān)視器,等待在該對象的線程等待隊列上,此時其他線程可以得到監(jiān)視器從而訪問該對象,之后可以通過調(diào)用notify/notifyAll方法來喚醒先前因調(diào)用wait方法而等待的線程。一般情況下,對于wait/notify/notifyAll方法的調(diào)用都是根據(jù)一定的條件來進行的。比如生產(chǎn)者/消費者問題中對于隊列空、滿的判斷。
[if !supportLists]1.1[endif]鎖帶來的問題:
死鎖:
例如兩個人必須共用一套餐具(一柄刀、一柄叉)輪流進餐,如果A持有刀,等待叉;B持有叉,等待刀,這兩個人均在等待對方釋放資源,導致死鎖。
避免死鎖的方式:
[if !supportLists]l[endif]所有的線程按照相同的資源順序獲得一組鎖。這種方法消除了刀和叉的擁有者分別等待對方的資源的問題。即兩個人都是以先獲取刀,再獲取叉的方式進行資源申請。
[if !supportLists]l[endif]將多個鎖組成一組并放到同一個鎖下,比如刀和叉組成一個餐具對象,申請刀或叉之前,必須先獲取這個組合對象餐具的鎖。
[if !supportLists]1.2[endif]鎖作用
鎖提供了兩種主要特性:互斥(mutual exclusion)和可見性(visibility)?;コ饧匆淮沃辉试S一個線程持有某個特定的鎖,因此可使用該特性實現(xiàn)對共享數(shù)據(jù)的協(xié)調(diào)訪問協(xié)議,這樣,一次就只有一個線程能夠使用該共享數(shù)據(jù)。可見性要更加復雜一些,它必須確保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的——如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發(fā)許多嚴重問題。
[if !supportLists]2[endif]對象鎖、類鎖作用
[if !supportLists]2.1[endif]Synchronized作用
java虛擬機中,每個對象和類在邏輯上都是和一個監(jiān)視器相關(guān)聯(lián)的。對于對象來說,相關(guān)聯(lián)的監(jiān)視器保護對象的實例變量。對于類來說,監(jiān)視器保護類的類變量。(如果一個對象沒有實例變量,或者一個類沒有變量,相關(guān)聯(lián)的監(jiān)視器就什么也不監(jiān)視。)
為了實現(xiàn)監(jiān)視器的排他性監(jiān)視能力,java虛擬機為每一個對象和類都關(guān)聯(lián)一個鎖。代表任何時候只允許一個線程擁有的特權(quán)。線程訪問實例變量或者類變量不需鎖。但是如果線程獲取了鎖,那么在它釋放這個鎖之前,就沒有其他線程可以獲取同樣數(shù)據(jù)的鎖。
對象鎖:是針對一個對象的,它只在該對象聲明一個標志位標識該對象是否擁有鎖,所以它只會鎖住當前的對象。一般一個對象鎖是對一個非靜態(tài)成員變量進行syncronized修飾,或者對一個非靜態(tài)方法進行syncronized修飾。
類鎖:是鎖住整個類的,當有多個線程來聲明這個類的對象的時候?qū)蛔枞钡綋碛羞@個類鎖的對象被銷毀或者主動釋放了類鎖。
鎖不具有繼承性。
類鎖實際上用對象鎖來實現(xiàn)。當虛擬機裝載一個class文件的時候,它就會創(chuàng)建一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。
一個線程可以多次對同一個對象上鎖。對于每一個對象,java虛擬機維護一個加鎖計數(shù)器,線程每獲得一次該對象,計數(shù)器就加1,每釋放一次,計數(shù)器就減1,當計數(shù)器值為0時,鎖就被完全釋放。