理解Java中的synchronized [’sɪŋkrənaɪzd]
問題
有如下一個類a
class A {
public synchronized void a() {
}
public synchronized void b() {
}
}
然後創建兩個對象
A a1 = new A();
A a2 = new A();
然後在兩個線程中併發訪問如下代碼:
Thread1 a1.a();
Thread2 a2.a();
請問二者能否構成線程同步?
如果A的定義是下面這種呢?
class A {
public static synchronized void a() {
}
public static synchronized void b() {
}
}
要點
- synchronized採用鎖同步的機制實現線程同步
- 線程在等待鎖釋放的過程中處於阻塞狀態
- 一把鎖可以同步所有對應的代碼
使用:
synchronized修飾代碼時會表明對應的鎖,所有表明這把鎖的代碼都會同步。
Synchronized主修修飾對象爲以下三種:
修飾普通方法 一個對象中的加鎖方法只允許一個線程訪問。但要注意這種情況下鎖的是訪問該方法的實例對象, 如果多個線程不同對象訪問該方法,則無法保證同步。
修飾靜態方法 由於靜態方法是類方法, 所以這種情況下鎖的是包含這個方法的類,也就是類對象;這樣如果多個線程不同對象訪問該靜態方法,也是可以保證同步的。
修飾代碼塊 其中普通代碼塊 如Synchronized(obj) 這裏的obj 可以爲類中的一個屬性、也可以是當前的對象,它的同步效果和修飾普通方法一樣;Synchronized方法 (obj.class)靜態代碼塊它的同步效果和修飾靜態方法類似。
Class A{
//鎖是this
public synchronized void a(){};
public synchronized void b(){};
//鎖是this.class
public synchronized static void x(){};
public synchronized static void y(){};
public void z(){
//指定鎖,該處指定爲this
synchronized(this){
}
}
}
this 鎖對應object,this.class 鎖對應 clss
所以第一種a1,a2兩個鎖是不同的鎖會併發,第二種是相同的鎖A.class不會併發;
另外,修飾代碼塊時指定的鎖就與所有這把鎖對應的代碼同步。
運行機制
簡單的說是這樣synchronized在實現同步功能時會聲明一把鎖,表明必須持有這把鎖的線程才能訪問執行,在一個線程需要執行這塊代碼時,會先嚐試持有這把指定的鎖,如果這個鎖已被別的線程持有,那麼該線程就會開始等待,直到這把鎖被釋放後纔會繼續執行,並且在執行時會一直持有這把鎖,以防止其他線程進入這把鎖控制的所有代碼,直到該線程執行完纔會釋放這把鎖。
上面說的很模糊,這個鎖是怎麼回事呢,看了這個synchronized底層語義原理之後大概意思就是說synchronized的對象鎖,鎖的指針指向的是monitor對象,每個對象有都存在着一個 monitor(管程) 與之關聯,在 java 虛擬機 hotSpot 中monitor於對象的關聯關係是由ObjectMonitor實現。
其中有_EntryList 和_WaitSet 兩個對象列表,多線程訪問synchronized修飾的代碼段時會先進入_EntryList,線程獲取到monitor之後進入 Owner 區域,monitor中的 count 會加一,線程調用 wait 方法之後會釋放 monitor,count–,線程進入 waitset等待喚醒(被其他線程調用notify/notifyAll),線程執行完畢也要是釋放monitor並復位。我注意到這個地方講到wait和鎖的關係,又想到以前面試被問過sleep() 和wait()的區別
sleep() 和wait()的區別
我當初提前看過一些面試題,就是這樣答的:
sleep()方法屬於Thread[θrɛd]類中的。而wait()方法是屬於Object類中的。sleep()是線程安全的,wait()線程不安全。
真正理解之後的回答:
sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他一直在持有monitor(未進入 Waitset中),當指定的時間到了又會自動恢復運行狀態。這就跟上面連到一起了
而當調用wait()方法的時候,線程會進入Waitset,釋放 monitor,進入等待此對象的Waitset(等待鎖定池),只有針對此對象調用notify/notifyAll方法後本線程才進入對象鎖定池準備。
notify和notifyall的區別
notify只會通知一個在等待的對象,而notifyAll會通知所有在等待的對象,並且所有對象都會繼續運行
wait此方法導致當前線程(稱之爲 T)將其自身放置在對象的等待集中Waitset(等待鎖定池),然後放棄此對象上的所有同步要求。
出於線程調度目的,在發生以下四種情況之一前,線程 T 被禁用,且處於休眠狀態:
- 其他某個線程調用此對象的 notify 方法,並且線程 T 碰巧被任選爲被喚醒的線程。
- 其他某個線程調用此對象的 notifyAll 方法。
- 其他某個線程中斷線程 T。大約已經到達指定的實際時間。但是,如果 timeout 爲零,則不考慮實際時間,在獲得通知前該線程將一直等待。
然後,從對象的等待集Waitset中刪除線程T,並重新進行線程調度(進入_EntryList)。然後,該線程以常規方式與其他線程競爭,以獲得在該對象上同步的權利;一旦獲得對該對象的控制權,該對象上的所有其同步聲明都將被恢復到以前的狀態。
這就是調用 wait方法時的情況。然後,線程 T 從 wait 方法的調用中返回。所以,從 wait 方法返回時,該對象和線程 T 的同步狀態與調用 wait 方法時的情況完全相同。
就是說多個線程等待的時候notifyall所有的等待線程都被通知了,這些線程進行競爭之後其中一個會得到鎖(也是要等待正在執行的線程先釋放鎖才行)
/**
*
*/
package com.b510.test;
/**
* java中的sleep()和wait()的區別
* @author Hongten
* @date 2013-12-10
*/
public class TestD {
public static void main(String[] args) {
new Thread(new Thread1()).start();
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread1...");
System.out.println("thread1 is waiting...");
try {
//調用wait()方法,線程會放棄對象鎖,進入等待此對象的等待鎖定池
TestD.class.wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread1 is going on ....");
System.out.println("thread1 is over!!!");
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread2....");
System.out.println("thread2 is sleep....");
//只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。
TestD.class.notify();
//==================
//區別
//如果我們把代碼:TestD.class.notify();給註釋掉,即TestD.class調用了wait()方法,但是沒有調用notify()
//方法,則線程永遠處於掛起狀態。
try {
//sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,
//但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
//在調用sleep()方法的過程中,線程不會釋放對象鎖。
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread2 is going on....");
System.out.println("thread2 is over!!!");
}
}
}
}
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
thread1 is going on ....
thread1 is over!!!
註釋TestD.class.notify();
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!