Простий сценарій, що використовує wait () та notify () у Java


181

Чи можу я отримати повний простий сценарій, тобто підручник, який підказує, як це слід використовувати, зокрема з чергою?

Відповіді:


269

wait()І notify()методи покликані забезпечити механізм, що дозволяє нитку до блоку , поки стан конкретного не буде виконано. Для цього я припускаю, що ви хочете написати реалізацію черги блокування, де у вас є резервна копія елементів фіксованого розміру.

Перше, що вам потрібно зробити, це визначити умови, які ви хочете, щоб методи чекали. У цьому випадку ви хочете, щоб put()метод блокувався, поки в магазині не буде вільного місця, і ви хочете, щоб take()метод блокувався, поки не з’явиться якийсь елемент, який потрібно повернути.

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Є декілька речей, які слід зазначити про спосіб використання механізмів очікування та оповіщення.

Під - перше, вам необхідно переконатися , що будь-які виклики wait()або notify()знаходяться в синхронному області коду (з wait()і notify()виклики синхронізуються на одному об'єкті). Причина цього (крім стандартних питань безпеки потоку) пов’язана з чимось відомим як пропущений сигнал.

Прикладом цього є те, що потік може зателефонувати, put()коли черга буває заповнена, вона перевіряє стан, бачить, що черга повна, проте, перш ніж вона може блокувати інший потік, запланована. Потім цей другий потік take()є елементом з черги і повідомляє потоки, що очікують, що черга вже не заповнена. Оскільки перший потік вже перевірив стан, він буде просто зателефонувати wait()після його повторного планування, навіть якщо він може досягти прогресу.

Синхронізуючи спільний об'єкт, ви можете переконатися, що ця проблема не виникає, оскільки take()виклик другого потоку не зможе досягти прогресу, поки перший потік фактично не заблокується.

По-друге, вам потрібно поставити умову, яку ви перевіряєте, в циклі часу, а не у випадку if, через проблему, відому як помилкові пробудження. Тут іноді чекаюча нитка може бути повторно активована без notify()виклику. Якщо встановити цю перевірку на певний час, ви будете гарантувати, що якщо трапиться помилкове пробудження, умова буде повторно перевірена, і потік wait()знову зателефонує .


Як згадували деякі інші відповіді, Java 1.5 представила нову бібліотеку одночасності (в java.util.concurrentпакеті), яка була розроблена для забезпечення абстрагування більш високого рівня над механізмом очікування / повідомлення. Використовуючи ці нові функції, ви можете переписати оригінальний приклад так:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Звичайно, якщо вам дійсно потрібна черга блокування, то вам слід використовувати реалізацію інтерфейсу BlockingQueue .

Також для таких матеріалів я дуже рекомендую Java Concurrency in Practice , оскільки він охоплює все, що ви могли б знати про проблеми та рішення, пов’язані з паралельною валютою.


7
@greuze, notifyпрокидається лише одна нитка. Якщо дві споживчі нитки змагаються за видалення елемента, одне сповіщення може розбудити іншу споживчу нитку, яка нічого не може зробити з цим і повернеться до сну (замість виробника, якого ми сподівалися вставити новий елемент). нитка виробника не прокидається, нічого не вставляється, і тепер усі три нитки будуть спати нескінченно. Я видалив свій попередній коментар, оскільки в ньому було сказано (помилково), що причиною проблеми стала несправедлива пробудження (Це не так)
finnw

1
@finnw Наскільки я можу сказати, проблему, яку ви помітили, можна вирішити за допомогою notifyAll (). Маю рацію?
Клінт Іствуд

1
Приклад, поданий тут @Jred, досить хороший, але має серйозне падіння. У коді всі методи позначені як синхронізовані, але НЕ ДВОХ СИНХРОНІЗОВАНИХ МЕТОДІВ НЕ МОЖЕ ВИКОРИСТУВАТИ В ОДНЕ ЧАС, то як з'явиться друга нитка на малюнку.
Шивам Агарвал

10
@ Brut3Forc3 вам потрібно прочитати javadoc файлу wait (): він говорить: Потік звільняє право власності на цей монітор . Отже, як тільки викликається wait (), монітор звільняється, і інший потік може виконати інший синхронізований метод черги.
JB Nizet

1
@JBNizet. "Прикладом цього є те, що потік може викликати put (), коли черга повна, вона перевіряє стан, бачить, що черга повна, проте, перш ніж вона зможе заблокувати інший потік, запланована". Ось як прийти другий потік запланований, якщо чекати ще не зателефонували
Шивам Аґгарвал

148

Не приклад черги, але надзвичайно простий :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Деякі важливі моменти:
1) НІКОЛИ не роби

 if(!pizzaArrived){
     wait();
 }

Завжди використовуйте while (умова), тому що

  • а) потоки можуть епізодично пробуджуватися з стану очікування, не повідомляючи про це нікого. (навіть коли хлопець з піци не задзвонив, хтось вирішив би спробувати їсти піцу.)
  • b) Ви повинні перевірити стан ще раз після придбання синхронізованого блокування. Скажімо, піца не триває вічно. Ви прокидаєтесь, вибудовуєтесь для піци, але цього недостатньо для всіх. Якщо ви не перевірите, ви можете з'їсти папір! :) (напевно, кращий приклад був би while(!pizzaExists){ wait(); }.

2) Ви повинні тримати замок (синхронізований) перед тим, як викликати функцію очікування / nofity. Нитки також повинні придбати замок перед прокиданням.

3) Намагайтеся уникати будь-якого блокування у вашому синхронізованому блоці та прагніть не посилатися на чужі методи (методи, які ви точно не знаєте, що вони роблять). Якщо вам доведеться, обов’язково вживайте заходів, щоб уникнути тупиків.

4) Будьте обережні з notify (). Дотримуйтесь notifyAll (), поки не дізнаєтеся, що ви робите.

5) Останнє, але не менш важливе, читайте на практиці Java Concurrency !


1
Не могли б ви детальніше розібратися, чому б не використовувати "if (! PizzaArrived) {wait ();}"?
Усі

2
@Everyone: Додано пояснення. HTH.
Енно Шіоджі

1
навіщо використовувати pizzaArrivedпрапор? якщо прапор буде змінено без виклику, notifyвін не матиме жодного ефекту. Так само працює з waitі notifyназиває приклад.
Пабло Фернандес

2
Я не розумію - нитка 1 виконує метод eatPizza () і вводить верхній синхронізований блок, і синхронізує клас MyHouse. Жодна піца ще не прибула, тому її просто чекають. Тепер нитка 2 намагається доставити піцу, викликавши метод pizzaGuy (); але не може, оскільки потоку 1 вже належить замок, і він не здає його (він постійно чекає). Ефективним результатом є тупиковий кут - нитка 1 чекає, що потік 2 виконає метод notifyAll (), тоді як нитка 2 чекає на потік 1, щоб відмовитись від блокування класу MyHouse ... Що мені не вистачає тут?
flamming_python

1
Ні, коли змінна охороняється synchronizedключовим словом, вона буде зайвою оголошувати змінну volatile, і рекомендується уникати її, щоб уникнути плутанини @mrida
Enno Shioji

37

Навіть якщо ви просили wait()і notify()конкретно, я відчуваю , що ця цитата ще досить важливо:

Josh Bloch, Ефективне Java 2-е видання , пункт 69: Віддавайте перевагу утилітам одночасності waitта notify(наголошуйте його):

Враховуючи складність використання waitта notifyкоректного використання, ви повинні використовувати утиліти одночасності вищого рівня замість [...] використання waitта notifyбезпосередньо подібні до програмування на "мові збірки паралельності", порівняно з мовою вищого рівня, що надається java.util.concurrent. Є рідкісний, якщо взагалі колись, привід використовувати waitі notifyв новому коді .


BlockingQueueS, наданий у пакеті java.util.concurrent, не є стійкими. Що ми можемо використовувати, коли черга має бути постійною? тобто, якщо система виходить з 20 елементів у черзі, мені потрібні ті, які повинні бути присутніми при перезапуску системи. Оскільки всі черги java.util.concurrent, схоже, є "в пам'яті", чи є якийсь спосіб їх використання як / hacked / overridden, щоб забезпечити реалізацію, здатну до стійкості?
Volksman

1
Можливо, резервна черга може бути забезпечена? тобто ми б забезпечили постійну реалізацію інтерфейсу черги.
Volksman

Це дуже добре notify()wait()
зазначити

7

Ви подивилися цей підручник з Java ?

Далі я б радив вам не бігти від гри з подібними речами в реальному програмному забезпеченні. Добре грати з ним, щоб ви знали, що це таке, але паралельність має підводні камені всюди. Краще використовувати абстракції вищого рівня та синхронізовані колекції або черги JMS, якщо ви створюєте програмне забезпечення для інших людей.

Це хоча б те, що я роблю. Я не є експертом з питань конкурентоспроможності, тому не можу вручну обробляти теми, коли це можливо.


2

Приклад

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}

0

Приклад для wait () та notifyall () у Threading.

У якості ресурсу використовується метод синхронізованого списку статичних масивів, якщо список масивів порожній. метод notify () викликається після додавання елемента для списку масивів.

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}

1
ПЗ перевірити цю лінію if(arrayList.size() == 0), я думаю, що тут може бути помилка.
Wizmann
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.