【Java 并发】任务之间的合作:wait() 、notify() notifyAll() 、lock+condition、BlockingQueue

The key issue when tasks are cooperating is handshaking between those tasks. To accomplish this handshaking, we use the same foundation: the mutex, which in this case guarantees that only one task can respond to a signal. This eliminates any possible race conditions. On top of the mutex, we add a way for a task to suspend itself until some external state changes (e.g., “The plumbing is now in place”), indicating that it’s time for that task to move forward. In this section, we’ll look at the issues of handshaking between tasks, which is safely implemented using the Object methods wait( ) and notifyAll( ). The Java SE5 concurrency library also provides Condition objects with await( ) and signal( ) methods.

wait() and notifyAll()

wait() 会等待其他任务改变当前环境,它使任务关闭并等待notify() notifyAll() 将它唤醒。
wait()使当前任务释放锁(sleep()和yield()不会),因此其他synchronized方法可以被调用。

Thus, when you call wait( ), you’re saying, “I’ve done all I can right now, so I’m going to wait right here, but I want to allow other synchronized operations to take place if they can.”

wait()两种使用方法:
1、和sleep()一样让程序暂停一会。此时对象释放锁,并且在暂停中可以被notify唤醒
2、最常用的方法,wait直到被notify唤醒。

无论是Thread还是Runnable,可以在任何同步方法中wait()。实际上,唯一可以调用wait(),notify()或notifyAll() 的位置是在synchronized方法或块内(由于它不操纵锁,因此可以在非同步方法内调用sleep())。
如果您在未synchronized方法中调用这些方法中的任何一个,程序将进行编译,但是在运行该程序时,您会收到一个IllegalMonitorStateException消息,消息有点不直观,即"current thread not owner."。该消息意味着,调用wait(),notify()或notifyAll() 的任务必须“own”(获取)对象的锁,然后才能调用这些方法。

使用wait()必须被while循环包围,以检查外部环境的变化

This is important because:
• You may have multiple tasks waiting on the same lock for the same reason, and the first task that wakes up might change the situation (even if you don’t do this someone might inherit from your class and do it). If that is the case, this task should be suspended again until its condition of interest changes.
• By the time this task awakens from its wait( ), it’s possible that some other task will have changed things such that this task is unable to perform or is uninterested in performing its operation at this time. Again, it should be resuspended by calling wait( ) again.
• It’s also possible that tasks could be waiting on your object’s lock for different reasons (in which case you must use notifyAll( )). In this case, you need to check whether you’ve been woken up for the right reason, and if not, call wait( ) again.

以下为例,我们需要将车上蜡wax -> 打磨polish -> 再次上蜡waxpolish之前检查是否上了蜡WaxOn,上第二层蜡也需要检查是否打磨。设置WaxOn为上蜡标志。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Car {
    private boolean waxOn = false;
    public synchronized void waxed() {
        waxOn = true; // Ready to Polish
        System.out.println("waxing finished!! next polish");
        notifyAll();
    }
    public synchronized void Polished() {
        waxOn = false; // Ready for another coat of wax
        System.out.println("polish finished !!  next wax ");
        notifyAll();
    }
    public synchronized void checkForWaxing() throws InterruptedException {
        while(waxOn == false){
            System.out.println(" not yet wax... need wax");
            wait();
        }

    }
    public synchronized void checkForPolishing() throws InterruptedException {
        while(waxOn == true){
            System.out.println("not yet polish... need polish");
            wait();
        }

    }
}
class Waxon implements Runnable{
    private Car car;
    public Waxon(Car c){car =c;}
    @Override
    public void run() {
    try{
        while (!Thread.interrupted()){
            TimeUnit.MILLISECONDS.sleep(200);
            car.waxed();
            car.checkForPolishing();
        }
    }catch (InterruptedException e){
        System.out.println(" Exiting via interrupt");
    }
        System.out.println("Ending wax on task");
    }
}
class Polish implements Runnable{
    private Car car;
    public Polish(Car c){car=c; }
    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                car.checkForWaxing();
                System.out.println("wax off!  wait for another wax ");
                TimeUnit.MILLISECONDS.sleep(200);
                car.Polished();
            }
        } catch(InterruptedException e) {
            System.out.println("Exiting via interrupt");
        }
        System.out.println("Ending Wax Off task");
    }
}

public class WaxOMatic {
    public static void main(String[] args) throws Exception{
        Car car=new Car();
        ExecutorService exec= Executors.newCachedThreadPool();
        exec.execute(new Waxon(car));
        exec.execute(new Polish(car));
        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}

在这里插入图片描述

使用wait和notify时要小心,特别容易产生死锁

T1:
synchronized(sharedMonitor) {
<setup condition for T2>
sharedMonitor.notify();
}
T2:
while(someCondition) {
// Point 1 CPU switch to t1,t1.notify() someCondition change,and back to T2.  sharedMonitor always wait
synchronized(sharedMonitor) {
sharedMonitor.wait();
  }
}
T2 should be 
synchronized(sharedMonitor) {
while(someCondition)
sharedMonitor.wait();
}

notify() vs. notifyAll()

Using notify( ) instead of notifyAll( ) is an optimization. Only one task of the possible many that are waiting on a lock will be awoken with notify( ), so you must be certain that the right task will wake up if you try to use notify( ). In addition, all tasks must be waiting on the same condition in order for you to use notify( ), because if you have tasks that are waiting on different conditions, you don’t know if the right one will wake up. If you use notify( ), only one task must benefit when the condition changes. Finally, these constraints must always be true for all possible subclasses. If any of these rules cannot be met, you must use notifyAll( ) rather than notify( ).

当使用notify()时,会唤醒一个任务,必须确认wait的那一个任务是所需要的。当不确定会唤醒哪一个任务时,需使用notifyAll();

notifyAll( ) wakes up “all waiting tasks.” Does this mean that any task that is in a wait( ), anywhere in the program, is awoken by any call to notifyAll( )…
in fact, only the tasks that are waiting on a particular lock are awoken when notifyAll( ) is called/or that lock:

Producers and consumers

import java.util.concurrent.*;
import java.util.concurrent.TimeUnit;

class Meal{
    private final int orderNum;
    public Meal(int orderNum) { this.orderNum = orderNum; }
    public String toString() { return "Meal " + orderNum; }
}

class WaitPerson implements Runnable {
    private Restuarant restaurant;
    public WaitPerson(Restuarant r) { restaurant = r; }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized(this) {
                    while(restaurant.meal == null)
                        wait(); // ... for the chef to produce a meal
                }
                System.out.println("Waitperson got " + restaurant.meal);
                synchronized(restaurant.chef) {
                    restaurant.meal = null;
                    restaurant.chef.notifyAll(); // Ready for another
                }
            }
        } catch(InterruptedException e) {
            System.out.println("WaitPerson interrupted");
        }
    }
}

class Chef implements Runnable {
    private Restuarant restaurant;
    private int count = 0;
    public Chef(Restuarant r) { restaurant = r; }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized(this) {
                    while(restaurant.meal != null)
                        wait(); // ... for the meal to be taken
                }
                if(++count == 10) {
                    System.out.println("Out of food, closing");
                    restaurant.exec.shutdownNow();
                }
                System.out.println("Order up! ");
                synchronized(restaurant.waitPerson) {
                    restaurant.meal = new Meal(count);
                    restaurant.waitPerson.notifyAll();
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch(InterruptedException e) {
            System.out.println("Chef interrupted");
        }
    }
}

public class Restuarant {
    Meal meal;
    ExecutorService exec = Executors.newCachedThreadPool();
    WaitPerson waitPerson = new WaitPerson(this);
    Chef chef = new Chef(this);
    public Restuarant() {
        exec.execute(chef);
        exec.execute(waitPerson);
    }
    public static void main(String[] args) {
        new Restuarant();
    }
}

Lock and Condition objects

The basic class that uses a mutex and allows task suspension is the Condition, and you can suspend a task by calling await( ) on a Condition. When external state changes take place that might mean that a task should continue processing, you notify the task by calling signal( ), to wake up one task, or signalAll( ), to wake up all tasks that have suspended themselves on that Condition object (as with notifyAll( ), signalAll( ) is the safer approach).

使用lock和condition重写WaxPolish.java
只需变动Car即可

class Car2 {
    private Lock lock= new ReentrantLock();
    private Condition condition= lock.newCondition();
    private boolean waxOn = false;
    public void waxed() {
        lock.lock();
        try {
            waxOn = true; // Ready to buff
            System.out.println("waxing finished!! next polish");
           condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
    public  void Polished() {
        lock.lock();
        try{
            waxOn = false; // Ready for another coat of wax
            System.out.println("polish finished !!  next wax ");
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
    public  void checkForWaxing() throws InterruptedException {
        lock.lock();
        try {
            while(waxOn == false){
                System.out.println(" not yet wax... need wax");
                condition.await();
            }
        }finally {
            lock.unlock();
        }


    }
    public void checkForPolishing() throws InterruptedException {
        lock.lock();
        try {
            while(waxOn == true){
                System.out.println("not yet polish... need polish");
                condition.await();
            }
        }finally {
            lock.unlock();
        }
    }
}

BlockingQueue

同一时刻只允许一个任务进行插入或者删除元素,java.util.concurrent.BlockingQueue
通常使用LinkedBlockingQueue,它会创建一个无界队列。ArrayBlockingQueue需要一个size值,在block之前只能放入相应大小的值。

These queues also suspend a consumer task if that task tries to get an object from the queue and the queue is empty, and resume when more elements become available.

以Toast为例,有三个线程,一个制作吐司,一个在吐司上抹黄油,一个在抹了黄油的吐司上涂果酱。(os:热量严重超标;) )以下使用BlockingQueue对他们进行管理,不使用显式同步方法(Lock 和synchronized)

import java.util.Random;
import java.util.concurrent.*;
import static net.mindview.util.Print.*;

class Toast {
    public enum Status { DRY, BUTTERED, JAMMED }
    private Status status = Status.DRY;
    private final int id;
    public Toast(int idn) { id = idn; }
    public void butter() { status = Status.BUTTERED; }
    public void jam() { status = Status.JAMMED; }
    public Status getStatus() { return status; }

    public int getId() { return id; }
    public String toString() {
        return "Toast " + id + ": " + status;
    }
}
class ToastQueue extends LinkedBlockingQueue<Toast> {}
class Toaster implements Runnable {
    private ToastQueue toastQueue;
    private int count = 0;
    private Random rand = new Random(47);
    public Toaster(ToastQueue tq) { toastQueue = tq; }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
// Make toast
                Toast t = new Toast(count++);
                print(t);
// Insert into queue
                toastQueue.put(t);
            }
        } catch(InterruptedException e) {
            print("Toaster interrupted");}
        print("Toaster off");
    }
}
// Apply butter to toast:
class Butterer implements Runnable {
    private ToastQueue dryQueue, butteredQueue;
    public Butterer(ToastQueue dry, ToastQueue buttered) {
        dryQueue = dry;
        butteredQueue = buttered;
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
                Toast t = dryQueue.take();
                t.butter();
                print(t);
                butteredQueue.put(t);
            }
        } catch(InterruptedException e) {
            print("Butterer interrupted");
        }
        print("Butterer off");
    }
}
class Jammer implements Runnable {
    private ToastQueue butteredQueue, finishedQueue;
    public Jammer(ToastQueue buttered, ToastQueue finished) {
        butteredQueue = buttered;
        finishedQueue = finished;
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
                Toast t = butteredQueue.take();
                t.jam();
                print(t);
                finishedQueue.put(t);
            }
        } catch(InterruptedException e) {
            print("Jammer interrupted");
        }
        print("Jammer off");
    }
}
class Eater implements Runnable {
    private ToastQueue finishedQueue;
    private int counter = 0;
    public Eater(ToastQueue finished) {
        finishedQueue = finished;
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
// Blocks until next piece of toast is available:
                Toast t = finishedQueue.take();
                // Verify that the toast is coming in order,
// and that all pieces are getting jammed:
                if(t.getId() != counter++ ||
                        t.getStatus() != Toast.Status.JAMMED) {
                    print(">>>> Error: " + t);
                    System.exit(1);
                } else
                    print("Eat  " + t);
            }
        } catch(InterruptedException e) {
            print("Eater interrupted");
        }
        print("Eater off");
    }
}
public class ToastOMatic {
    public static void main(String[] args) throws Exception {
        ToastQueue dryQueue = new ToastQueue(),
                butteredQueue = new ToastQueue(),
                finishedQueue = new ToastQueue();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Toaster(dryQueue));
        exec.execute(new Butterer(dryQueue, butteredQueue));
        exec.execute(new Jammer(butteredQueue, finishedQueue));
        exec.execute(new Eater(finishedQueue));
        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}
发布了27 篇原创文章 · 获赞 1 · 访问量 693

猜你喜欢

转载自blog.csdn.net/SUKI547/article/details/101450139