淺談多執行緒之鎖的機制
Java中鎖的機制
synchronized–Java語言的關鍵字,當它用來修飾一個方法或者一個程式碼塊的時候,能夠保證在同一時刻最多隻有一個執行緒執行該段程式碼。
- 當兩個併發執行緒訪問同一個物件Object中的這個synchronized同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊後才能執行該程式碼塊。
- 然而,當一個執行緒訪問Object的一個synchronized同步程式碼塊時,另一個執行緒仍然可以訪問該Object中的非synchronized同步程式碼塊。
- 尤其關鍵的是,當一個執行緒訪問Object的一個同步程式碼塊時,其他執行緒對Object中所有其他同步程式碼塊的訪問將被阻塞。也就是說,當一個執行緒訪問Object的一個同步程式碼塊時,他就獲得了這個Object的物件鎖。結果,其他執行緒對該Object物件所有同步程式碼部分的訪問都被暫時阻塞。
注:只能放在方法頭(限定修飾符public、private之後,返回值void、Object之前)和方法內部使用。
例1:
public class ThreadTest {
public static void main(String[] args) {
final SynchronizedTest test=new SynchronizedTest();
//執行緒1
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
test.test("thread1");
}
});
//執行緒2
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
test.test("thread2");
}
});
thread1.start();
thread2.start();
}
}
public class SynchronizedTest {
public synchronized void test(String name){
for(int i=0;i<5;i++){
System.out.println(name+"執行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
列印結果
thread1執行
thread1執行
thread1執行
thread1執行
thread1執行
thread2執行
thread2執行
thread2執行
thread2執行
thread2執行
例2:兩個併發執行緒一個呼叫同步方法,一個呼叫普通方法
//執行緒2
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
test.test3("thread2");//上面是test方法
}
});
//SynchronizedTest 類中新增的普通方法
public void test3(String name){
for(int i=0;i<5;i++){
System.out.println(name+"執行");
}
}
/*執行結果
thread1執行
thread2執行
thread2執行
thread2執行
thread2執行
thread2執行
thread1執行
thread1執行
thread1執行
thread1執行*/
例3:
//執行緒2
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
test.test3("thread2");//上面是test方法
}
});
//SynchronizedTest 類中新增的同步方法test2
public synchronized void test2(String name){
for(int i=0;i<5;i++){
System.out.println(name+"執行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*執行結果
thread1執行
thread1執行
thread1執行
thread1執行
thread1執行
thread2執行
thread2執行
thread2執行
thread2執行
thread2執行*/
方法鎖(synchronized修飾方法時)
通過在方法宣告中加入 synchronized關鍵字來宣告 synchronized 方法。
synchronized 方法控制對類成員變數的訪問:
每個類例項對應一把鎖,每個 synchronized 方法都必須獲得呼叫該方法的類例項的鎖方能執行,否則所屬執行緒阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的執行緒方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類例項,其所有宣告為 synchronized 的成員函式中至多隻有一個處於可執行狀態,從而有效避免了類成員變數的訪問衝突。
物件鎖(synchronized修飾方法或程式碼塊)
當一個物件中有synchronized method或synchronized block的時候呼叫此物件的同步方法或進入其同步區域時,就必須先獲得物件鎖。如果此物件的物件鎖已被其他呼叫者佔用,則需要等待此鎖被釋放。(方法鎖也是物件鎖)
java的所有物件都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。執行緒進入synchronized方法的時候獲取該物件的鎖,當然如果已經有執行緒獲取了這個物件的鎖,那麼當前執行緒會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放物件鎖。這裡也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然可以由JVM來自動釋放。
物件鎖的兩種形式:
public class Test
{
// 物件鎖:形式1(方法鎖)
public synchronized void Method1()
{
System.out.println("我是物件鎖也是方法鎖");
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
// 物件鎖:形式2(程式碼塊形式)
public void Method2()
{
synchronized (this)
{
System.out.println("我是物件鎖");
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
類鎖(synchronized 修飾靜態的方法或程式碼塊)
由於一個class不論被例項化多少次,其中的靜態方法和靜態變數在記憶體中都只有一份。所以,一旦一個靜態的方法被申明為synchronized。此類所有的例項化物件在呼叫此方法,共用同一把鎖,我們稱之為類鎖。
物件鎖是用來控制例項方法之間的同步,類鎖是用來控制靜態方法(或靜態變數互斥體)之間的同步。
類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定例項方法和靜態方法的區別的。
java類可能會有很多個物件,但是隻有1個Class物件,也就是說類的不同例項之間共享該類的Class物件。Class物件其實也僅僅是1個java物件,只不過有點特殊而已。由於每個java物件都有1個互斥鎖,而類的靜態方法是需要Class物件。所以所謂的類鎖,不過是Class物件的鎖而已。獲取類的Class物件有好幾種,最簡單的就是[類名.class]的方式。
public class Test
{
// 類鎖:形式1
public static synchronized void Method1()
{
System.out.println("我是類鎖一號");
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
// 類鎖:形式2
public void Method2()
{
synchronized (Test.class)
{
System.out.println("我是類鎖二號");
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}