введение
Эта вещь пришла мне в голову, когда я сегодня наблюдал за многопоточным общением: почему межпотоковое взаимодействие, такое как wait(), notify(), notifyAll(), нужно размещать в синхронизированном блоке, другими словами, зачем использовать синхронизировано. Что делать, если метод wait() не находится в синхронизированном блоке:
@Test
public void test() {
try {
new Object().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
оказаться:
Погуглив, просматривая блоги разных великих богов, я наконец нашел ответ.
Lost Wake-Up Problem
Во-первых, давайте возьмем пример, поток-потребитель и поток-производитель. Задача производителя — count+1, а затем пробуждает потребителя; задача потребителя — count-1 и засыпает, когда count равен 0.
Псевдокод производителя:
count++;
notify();
Потребительский псевдокод:
while(count <= 0){
wait();
count--;
}
Друзья, знакомые с многопоточностью, должны с первого взгляда увидеть проблему, что будет, если смешать шаги производителя и потребителя.
Прежде всего, предположим, что count = 0. В это время потребитель проверяет значение count и обнаруживает, что условие count
Изображение изПочему wait() делает это
Это так называемая потерянная проблема пробуждения
Как решить
Теперь мы должны увидеть, что корень проблемы в том, что счетчик может измениться между потребителем, проверяющим счетчик, и вызовом wait().
Итак, как мы ее решим?
Пусть потребитель и производитель конкурируют за блокировку, и когда конкуренция достигнута, значение count может быть изменено.
Итак, код производителя:
lock();
count++;
notify();
unlock();
Код потребителя:
lock();
while(count <= 0){
wait();
count--;
}
unlock();
Теперь давайте посмотрим, действительно ли это решает проблему?
Ответ в том, что это бесполезно, все равно будет проблема с потерянным пробуждением, а производительность такая же, как и у безблокировки.
Потому что wait() освобождает блокировку, а затем ожидает получения блокировки.Конечно, блокировка должна быть получена первой, но здесь нет цепочки.
окончательный ответ
Итак, мы можем сделать вывод, что для того, чтобы избежать этой проблемы с потерей пробуждения, в этой модели мы всегда должны помещать наш код в синхронизированный блок.
Java требует, чтобы наши вызовы wait()/notify() находились в синхронизированном блоке, он просто не хочет, чтобы мы случайно столкнулись с этой потерянной проблемой пробуждения.
Не только эти два метода, включая await()/signal() из java.util.concurrent.locks.Condition, также должны быть в синхронизированном блоке.
правильный:
private Object obj = new Object();
private Object anotherObj = new Object();
@Test
public void produce() {
synchronized (obj) {
try {
//同步块要对当前线程负责,而不是anotherObj.notify();
obj.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Использование ожидания и уведомления
ждать (), уведомлять () и уведомлять всех ()
-
Методы wait(), notify() и notifyAll() являются локальными и окончательными и не могут быть переопределены.
-
Вызов метода wait() объекта может заблокировать текущий поток, а текущий поток должен владеть монитором объекта (т. е. блокировкой или монитором).
-
Вызов метода notify() объекта может разбудить поток, ожидающий монитор этого объекта.Если имеется несколько потоков, ожидающих монитор этого объекта, можно разбудить только один поток.
-
Вызовите метод notifyAll(), чтобы разбудить все потоки, ожидающие монитора этого объекта.
применение
/**
* wait() && notify()方法
* 这两个方法是在Object中定义的,用于协调线程同步,比 join 更加灵活
*/
public class NotifyDemo {
public static void main(String[] args) {
//写两个线程 1.图片下载
Object obj=new Object();
Thread download=new Thread(){
public void run() {
System.out.println("开始下载图片");
for (int i = 0; i < 101; i+=10) {
System.out.println("down"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("图片下载成功");
synchronized (obj) {
obj.notify();//唤起
}
System.out.println("开始下载附件");
for (int i = 0; i < 101; i+=10) {
System.out.println("附件下载"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("附件下载成功");
}
};
//2.图片展示
Thread show=new Thread(){
public void run(){
synchronized (obj) {
try {
obj.wait();//阻塞当前
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:开始展示图片");
System.out.println("图片展示完毕");
}
}
};
download.start();
show.start();
}
}