參考文獻: java synchronized詳解
- synchronized 修飾方法
public synchronized void accessVal(int newVal);
synchronized 方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執(zhí)行,否則所屬線程阻塞,方法一旦執(zhí)行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執(zhí)行狀態(tài)。這種機制確保了同一時刻對于每一個類實例,其所有聲明為 synchronized 的成員函數(shù)中至多只有一個處于可執(zhí)行狀態(tài)(因為至多只有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為 synchronized)。 在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態(tài)成員函數(shù)聲明為 synchronized ,以控制其對類的靜態(tài)成員變量的訪問。
- 修飾代碼塊
synchronized (object) {
waitThread.start();
try {
System.out.print("主線程等待\n");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
獲得的鎖是傳入的 類的實例中的鎖
有如下一個例子:
public class Main {
private static dog object = new dog();
public static void main(String[] args) {
System.out.print("主線程運行\(zhòng)n");
WaitThread waitThread = new WaitThread();
//waitThread.start();
synchronized (object) {
waitThread.start(); //注意此處放的地方
try {
System.out.print("主線程等待\n");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("主線程結束\n");
}
static class WaitThread extends Thread {
@Override
public void run() {
try {
synchronized (object) {
Thread.sleep(3000);
System.out.print("主線程喚起\n");
object.notifyAll();
}
} catch (Exception e) {
}
}
}
}
結果:
主線程運行
主線程等待
(sleep3秒)
主線程喚起
主線程結束
Process finished with exit code 0
請注意上述例子中的waitThread.start(); 執(zhí)行的地方,是放在了synchronized(Objedt) {} 塊中;如果將此代碼放到它的外邊也就是注釋掉的地方,就會有問題:
主線程運行
(sleep3秒)
主線程喚起
主線程等待
因為在執(zhí)行 run方法時已經(jīng)持有了object 的鎖,主線程中的synchronized 的實例object鎖 已經(jīng)被持有了,所以不能執(zhí)行,等子線程中的鎖釋放之后主線程才可以執(zhí)行
注意點:wait() , notify() ,notifyAll() ,必須放在synchronized block 中,否則會拋異常。也就是說它們都是線程不安全的。
在做測試的時候每次wait()之后的nofity()時線程在爭奪同一把鎖的時候是隨機的,誰搶到就給誰。
notifyAll() 讓我想起了在做驅動時的一種驚群效應,一旦鎖被釋放了,所有的wait線程被喚醒。
對于對象obj來說:
obj.wait():該方法的調用,使得調用該方法的執(zhí)行線程(T1)放棄obj的對象鎖并阻塞,直到別的線程調用了obj的notifyAll方法、或者別的線程調用了obj的notify方法且JVM選擇喚醒(T1),被喚醒的線程(T1)依舊阻塞在wait方法中,與其它的線程一起爭奪obj的對象鎖,直到它再次獲得了obj的對象鎖之后,才能從wait方法中返回。(除了notify方法,wait還有帶有時間參數(shù)的版本,在等待了超過所設時間之后,T1線程一樣會被喚醒,進入到爭奪obj對象鎖的行列;另外中斷可以直接跳出wait方法)
obj.notify():該方法的調用,會從所有正在等待obj對象鎖的線程中,喚醒其中的一個(選擇算法依賴于不同實現(xiàn)),被喚醒的線程此時加入到了obj對象鎖的爭奪之中,然而該notify方法的執(zhí)行線程此時并未釋放obj的對象鎖,而是離開synchronized代碼塊時釋放。因此在notify方法之后,synchronized代碼塊結束之前,所有其他被喚醒的,等待obj對象鎖的線程依舊被阻塞。
obj.notifyAll():與notify的區(qū)別是,該方法會喚醒所有正在等待obj對象鎖的線程。(不過同一時刻,也只有一個線程可以擁有obj的對象鎖)
另Thread.join() : 阻塞當前調用join函數(shù)時所在的線程,直到接受線程執(zhí)行完畢之后再繼續(xù),有點類似wait,但是這個是Thread調用,阻塞當前調用線程的,并直到執(zhí)行的線程執(zhí)行完畢之后才不阻塞當前線程
Thread.yeid() :使該函數(shù)的線程讓出CPU固定的時間。主動讓出線程的執(zhí)行權給其他線程。因為讓的的線程有可能被線程調度程序再次選中,所以有可能沒有效果(親測確實如此),謹慎使用。
public static void main(String[] args) {
Mrunable1 thread1 = new Mrunable1("A\n");
Mrunable1 thread2 = new Mrunable1("B\n");
thread1.start();
thread2.start();
}
static class Mrunable1 extends Thread {
public Mrunable1(String name) {
super(name);
}
@Override
public synchronized void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(Thread.currentThread().getName());
Thread.yield();
}
}
