Интервьюер: Почему метод wait() следует размещать в синхронизированном блоке?

Java

введение

Эта вещь пришла мне в голову, когда я сегодня наблюдал за многопоточным общением: почему межпотоковое взаимодействие, такое как 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();
    }
}