繁体   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