簡體   English   中英

帶等待/通知和不帶它們的同步塊之間的區別?

[英]Difference between Synchronized block with wait/notify and without them?

如果我只使用synchronized ,而不是wait / notify方法,它仍然是線程安全的嗎?

有什么不同?

使用synchronized使一個方法/塊一次只能在線程上訪問。 所以,是的,它是線程安全的。

這兩個概念是結合在一起的,而不是相互排斥的。 當您使用wait()您需要擁有該對象上的監視器。 所以你需要在它之前synchronized(..) 使用.wait()使當前線程停止,直到另一個線程對其等待的對象調用.notify() 這是對synchronized的補充,它只是確保只有一個線程會進入一個塊/方法。

所以剛剛在面試問題上尷尬之后,我決定再查一遍,第 10 億次再理解一遍。

synchronized塊使代碼線程安全。 毫無疑問。 wait()notify()notifyAll()出現時,您將嘗試編寫更高效的代碼。 例如,如果您有一個多個線程共享的項目列表,那么如果您將它放在監視器的synchronized塊中,那么線程線程將不斷跳入並在上下文切換期間來回來回運行代碼...... ..即使有一個空列表!

因此,在監視器(synchronized(..) 中的對象)上使用 wait() 作為一種機制,告訴所有線程冷靜下來並停止使用 CPU 周期,直到進一步通知或 notifyAll()。

所以像:

synchronized(monitor) {
    if( list.isEmpty() )
        monitor.wait();
}

...別的地方...

synchronized(monitor){
    list.add(stuff);
    monitor.notifyAll();
}

同步方法有兩個作用:

首先,對同一對象的同步方法的兩次調用不可能交錯。 當一個線程正在為一個對象執行同步方法時,所有其他調用同一個對象的同步方法的線程都會阻塞(掛起執行),直到第一個線程完成對對象的處理

其次,當一個同步方法退出時,它會自動建立一個發生在同一個對象的同步方法的任何后續調用之前的關系。 這保證了對象狀態的更改對所有線程都是可見的。

同步幫助您保護關鍵代碼。

如果要在多個線程之間建立通信,必須使用wait()notify() / notifyAll()

wait() :使當前線程等待,直到另一個線程為此對象調用 notify() 方法或 notifyAll() 方法。

notify() :喚醒在此對象的監視器上等待的單個線程。 如果有任何線程正在等待該對象,則選擇其中一個線程被喚醒。

notifyAll() :喚醒在此對象監視器上等待的所有線程。 線程通過調用等待方法之一在對象的監視器上等待。

使用 wait() 和 notify() 的簡單用例:生產者和消費者問題

消費者線程必須等到生產者線程產生數據。 wait() 和 notify() 在上述場景中很有用。 在一段時間內,已經引入了更好的替代方案。 請參閱此高級並發教程頁面。

簡單來說:

使用synchronized來保護數據的關鍵部分並保護您的代碼。

如果您想以安全的方式在相互依賴的多個線程之間建立通信,請使用wait()notify()以及同步。

相關 SE 問題:

“同步”是什么意思?

java中使用wait()和notify()的簡單場景

有效的 Java 條款 69:“鑒於正確使用等待和通知的困難,您應該改用更高級別的並發實用程序。”

避免使用wait() 和notify():盡可能使用synchronizedjava.util.concurrent 中的其他實用程序。

如果“同一對象”的 2 個線程嘗試獲取鎖,則使用同步塊。 由於對象類持有鎖,它知道給誰。 然而,如果 2 個對象的 2 個線程(比如 t2 和 t4)(obj1 的 t1 & t2 和 obj 2 的 t3 & t4)嘗試獲取鎖,obj1 將不知道 obj2 的鎖,而 obj2 將不知道 obj1 的鎖。 因此使用了等待和通知方法。

例如:

//example of java synchronized method  
class Table{  
 synchronized void printTable(int n){//synchronized method  
   for(int i=1;i<=5;i++){  
     System.out.println(n*i);  
     try{  
      Thread.sleep(400);  
     }catch(Exception e){System.out.println(e);}  
   }  

 }  
}  

class MyThread1 extends Thread{  
Table t;  
MyThread1(Table t){  
this.t=t;  
}  
public void run(){  
t.printTable(5);  
}  

}  
class MyThread2 extends Thread{  
Table t;  
MyThread2(Table t){  
this.t=t;  
}  
public void run(){  
t.printTable(100);  
}  
}  

public class TestSynchronization2{  
public static void main(String args[]){  
Table obj = new Table();//only one object  
MyThread1 t1=new MyThread1(obj);  
MyThread2 t2=new MyThread2(obj);  
t1.start();  
t2.start();  
}  
} 

兩個線程 t1 和 t2 屬於同一個對象,因此同步在這里工作正常。 然而,

class Table{  
 synchronized void printTable(int n){//synchronized method  
   for(int i=1;i<=5;i++){  
     System.out.println(n*i);  
     try{  
      Thread.sleep(400);  
     }catch(Exception e){System.out.println(e);}  
   }  

 }  
}  

class MyThread1 extends Thread{  
Table t;  
MyThread1(Table t){  
this.t=t;  
}  
public void run(){  
t.printTable(5);  
}  

}  
class MyThread2 extends Thread{  
Table t;  
MyThread2(Table t){  
this.t=t;  
}  
public void run(){  
t.printTable(100);  
}  
}  

public class TestSynchronization2{  
public static void main(String args[]){  
Table obj = new Table();
Table obj1 = new Table();
MyThread1 t1=new MyThread1(obj);  
MyThread2 t2=new MyThread2(obj1);  
t1.start();  
t2.start();  
}  
} 

當您運行上述程序時,由於每個線程屬於不同的對象,因此同步不起作用,因此您應該在這里使用等待和通知。

當你想等待一些條件(例如,用戶輸入)的同步塊等待/通知是必需的。

典型用法:

synchronized(obj) {
    // do something

    while(some condition is not met) {
        obj.wait();
    }
    // do something other
}

假設您不使用wait()。 然后,您必須實現繁忙循環輪詢所需的條件,這對性能不利。

synchronized(obj) {
    // do something

    while(some condition is not met) { // busy loop }

    // do something other
}

重要提示:即使一個線程被其他線程的 notify() 或 notifyAll() 喚醒,喚醒線程也不能保證立即恢復執行。 如果有其他線程等待在同一對象上執行同步塊,則喚醒線程應與線程競爭。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM