Відповіді:
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 , оскільки він охоплює все, що ви могли б знати про проблеми та рішення, пов’язані з паралельною валютою.
Не приклад черги, але надзвичайно простий :)
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 (умова), тому що
while(!pizzaExists){ wait(); }
.2) Ви повинні тримати замок (синхронізований) перед тим, як викликати функцію очікування / nofity. Нитки також повинні придбати замок перед прокиданням.
3) Намагайтеся уникати будь-якого блокування у вашому синхронізованому блоці та прагніть не посилатися на чужі методи (методи, які ви точно не знаєте, що вони роблять). Якщо вам доведеться, обов’язково вживайте заходів, щоб уникнути тупиків.
4) Будьте обережні з notify (). Дотримуйтесь notifyAll (), поки не дізнаєтеся, що ви робите.
5) Останнє, але не менш важливе, читайте на практиці Java Concurrency !
pizzaArrived
прапор? якщо прапор буде змінено без виклику, notify
він не матиме жодного ефекту. Так само працює з wait
і notify
називає приклад.
synchronized
ключовим словом, вона буде зайвою оголошувати змінну volatile
, і рекомендується уникати її, щоб уникнути плутанини @mrida
Навіть якщо ви просили wait()
і notify()
конкретно, я відчуваю , що ця цитата ще досить важливо:
Josh Bloch, Ефективне Java 2-е видання , пункт 69: Віддавайте перевагу утилітам одночасності wait
та notify
(наголошуйте його):
Враховуючи складність використання
wait
таnotify
коректного використання, ви повинні використовувати утиліти одночасності вищого рівня замість [...] використанняwait
таnotify
безпосередньо подібні до програмування на "мові збірки паралельності", порівняно з мовою вищого рівня, що надаєтьсяjava.util.concurrent
. Є рідкісний, якщо взагалі колись, привід використовуватиwait
іnotify
в новому коді .
notify()
wait()
Ви подивилися цей підручник з Java ?
Далі я б радив вам не бігти від гри з подібними речами в реальному програмному забезпеченні. Добре грати з ним, щоб ви знали, що це таке, але паралельність має підводні камені всюди. Краще використовувати абстракції вищого рівня та синхронізовані колекції або черги JMS, якщо ви створюєте програмне забезпечення для інших людей.
Це хоча б те, що я роблю. Я не є експертом з питань конкурентоспроможності, тому не можу вручну обробляти теми, коли це можливо.
Приклад
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
}
}
Приклад для 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());
}
}
if(arrayList.size() == 0)
, я думаю, що тут може бути помилка.
notify
прокидається лише одна нитка. Якщо дві споживчі нитки змагаються за видалення елемента, одне сповіщення може розбудити іншу споживчу нитку, яка нічого не може зробити з цим і повернеться до сну (замість виробника, якого ми сподівалися вставити новий елемент). нитка виробника не прокидається, нічого не вставляється, і тепер усі три нитки будуть спати нескінченно. Я видалив свій попередній коментар, оскільки в ньому було сказано (помилково), що причиною проблеми стала несправедлива пробудження (Це не так)