[java多线程]多线程同步(二)——wait, notify, notifyAll, join以及sleep

来源:互联网 发布:linux java dump命令 编辑:程序博客网 时间:2024/05/31 13:16

我们先来看一下Object类中wait, notify和notifyall的定义:

public class Object {    private static native void registerNatives();    static {        registerNatives();    }    /**     * Wakes up a single thread that is waiting on this object's      * monitor. If any threads are waiting on this object, one of them      * is chosen to be awakened. The choice is arbitrary and occurs at      * the discretion of the implementation. A thread waits on an object's      * monitor by calling one of the <code>wait</code> methods.     * <p>     * The awakened thread will not be able to proceed until the current      * thread relinquishes the lock on this object. The awakened thread will      * compete in the usual manner with any other threads that might be      * actively competing to synchronize on this object; for example, the      * awakened thread enjoys no reliable privilege or disadvantage in being      * the next thread to lock this object.     * <p>     * This method should only be called by a thread that is the owner      * of this object's monitor. A thread becomes the owner of the      * object's monitor in one of three ways:      * <ul>     * <li>By executing a synchronized instance method of that object.      * <li>By executing the body of a <code>synchronized</code> statement      *     that synchronizes on the object.      * <li>For objects of type <code>Class,</code> by executing a      *     synchronized static method of that class.      * </ul>     * <p>     * Only one thread at a time can own an object's monitor.      *     * @exception  IllegalMonitorStateException  if the current thread is not     *               the owner of this object's monitor.     * @see        java.lang.Object#notifyAll()     * @see        java.lang.Object#wait()     */    public final native void notify();    /**     * Wakes up all threads that are waiting on this object's monitor. A      * thread waits on an object's monitor by calling one of the      * <code>wait</code> methods.     * <p>     * The awakened threads will not be able to proceed until the current      * thread relinquishes the lock on this object. The awakened threads      * will compete in the usual manner with any other threads that might      * be actively competing to synchronize on this object; for example,      * the awakened threads enjoy no reliable privilege or disadvantage in      * being the next thread to lock this object.     * <p>     * This method should only be called by a thread that is the owner      * of this object's monitor. See the <code>notify</code> method for a      * description of the ways in which a thread can become the owner of      * a monitor.      *     * @exception  IllegalMonitorStateException  if the current thread is not     *               the owner of this object's monitor.     * @see        java.lang.Object#notify()     * @see        java.lang.Object#wait()     */    public final native void notifyAll();    /**     * Causes the current thread to wait until either another thread invokes the      * {@link java.lang.Object#notify()} method or the      * {@link java.lang.Object#notifyAll()} method for this object, or a      * specified amount of time has elapsed.      * <p>     * The current thread must own this object's monitor.      * <p>     * This method causes the current thread (call it <var>T</var>) to      * place itself in the wait set for this object and then to relinquish      * any and all synchronization claims on this object. Thread <var>T</var>      * becomes disabled for thread scheduling purposes and lies dormant      * until one of four things happens:     * <ul>     * <li>Some other thread invokes the <tt>notify</tt> method for this      * object and thread <var>T</var> happens to be arbitrarily chosen as      * the thread to be awakened.      * <li>Some other thread invokes the <tt>notifyAll</tt> method for this      * object.      * <li>Some other thread {@linkplain Thread#interrupt() interrupts}      * thread <var>T</var>.      * <li>The specified amount of real time has elapsed, more or less.  If      * <tt>timeout</tt> is zero, however, then real time is not taken into      * consideration and the thread simply waits until notified.      * </ul>     * The thread <var>T</var> is then removed from the wait set for this      * object and re-enabled for thread scheduling. It then competes in the      * usual manner with other threads for the right to synchronize on the      * object; once it has gained control of the object, all its      * synchronization claims on the object are restored to the status quo      * ante - that is, to the situation as of the time that the <tt>wait</tt>      * method was invoked. Thread <var>T</var> then returns from the      * invocation of the <tt>wait</tt> method. Thus, on return from the      * <tt>wait</tt> method, the synchronization state of the object and of      * thread <tt>T</tt> is exactly as it was when the <tt>wait</tt> method      * was invoked.      * <p>     * A thread can also wake up without being notified, interrupted, or     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely     * occur in practice, applications must guard against it by testing for     * the condition that should have caused the thread to be awakened, and     * continuing to wait if the condition is not satisfied.  In other words,     * waits should always occur in loops, like this one:     * <pre>     *     synchronized (obj) {     *         while (<condition does not hold>)     *             obj.wait(timeout);     *         ... // Perform action appropriate to condition     *     }     * </pre>     * (For more information on this topic, see Section 3.2.3 in Doug Lea's     * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,     * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming     * Language Guide" (Addison-Wesley, 2001).     *     * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()     * interrupted} by any thread before or while it is waiting, then an     * <tt>InterruptedException</tt> is thrown.  This exception is not     * thrown until the lock status of this object has been restored as     * described above.     *     * <p>     * Note that the <tt>wait</tt> method, as it places the current thread      * into the wait set for this object, unlocks only this object; any      * other objects on which the current thread may be synchronized remain      * locked while the thread waits.     * <p>     * This method should only be called by a thread that is the owner      * of this object's monitor. See the <code>notify</code> method for a      * description of the ways in which a thread can become the owner of      * a monitor.      *     * @param      timeout   the maximum time to wait in milliseconds.     * @exception  IllegalArgumentException      if the value of timeout is     *             negative.     * @exception  IllegalMonitorStateException  if the current thread is not     *               the owner of the object's monitor.     * @exception  InterruptedException if any thread interrupted the     *             current thread before or while the current thread     *             was waiting for a notification.  The <i>interrupted     *             status</i> of the current thread is cleared when     *             this exception is thrown.     * @see        java.lang.Object#notify()     * @see        java.lang.Object#notifyAll()     */    public final native void wait(long timeout) throws InterruptedException;    public final void wait(long timeout, int nanos) throws InterruptedException {        if (timeout < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException(                "nanosecond timeout value out of range");        }    if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {        timeout++;    }    wait(timeout);    }        public final void wait() throws InterruptedException {        wait(0);    }}

notify的javadoc说了一个很重要的内容,

一个线程要成为一个对象的监控器(内在锁)的拥有者,有以下三种方式:
1. 执行那个对象的synchronized实例方法;
2. 执行synchronized块,以那个对象为同步对象;
3. 对于类对象(java.lang.Class),执行那个类的synchronized静态方法;
同一时刻,只有一个线程拥有一个对象的监控器(内在锁)。

ps:我觉得内在锁更容易理解,后面我都用这个名词。


一、wait()方法

从代码可以看出可以看出wait()方法其实就是调用的wait(0),而wait(long timeout)的作用是使当前线程进入等待状态,直到另一个线程为某个对象调用notify或notifyAll方法,或者超过指定时间。

当前线程必须拥有调用wait()方法的对象的内在锁,javadoc中有下面的代码,很明显这个是用的获取对象内在锁的第2种方式。

synchronized (obj) {     while (<condition does not hold>)         obj.wait(timeout);     ... // Perform action appropriate to condition}
这个方法使得当前线程(比方说是线程T)将自身置于调用wait方法对象的wait set,并释放同步对象的内在锁。

注意,当前线程在等待时,线程中其它需要同步的对象仍然处于加锁状态;

线程T不能线程调度,并且在直到下面四种情况发生前都会处于休眠状态:

1. 其它线程调用等待对象的notify方法,并且线程T碰巧被选中为唤醒线程;

2. 其它线程调用等待对象的notifyAll方法;

3. 其它线程中断了线程T;

4. 指定的超时时间已经过了。然而如果超时时间设置为0,时间将不作考虑,线程在被notify之前只会一直等待;

线程还可能被虚假唤醒,虽然实际中很少发生,但在应用中对唤醒线程进行测试,如果条件不满足,必须使虚假唤醒的线程继续等待。所以wait()方法总是在循环中执行。

线程T从wait set中移除,并且可以进行线程调度。它和其它线程一样竞争同步使用对象的权利。线程T从wait()方法请求中返回时,同步对象和线程T的状态都完全和wait()方法被调用时一样。


二、notify()方法

唤醒一个等待对象内在锁的线程。如果有多个线程等待对象内部锁,其中一个被选择随机的唤醒。

被唤醒的线程不会执行,直到当前线程交出对象的内在锁。被唤醒的线程将和其它线程完全平等的竞争对象的同步块访问。

我们先来看一个wait()和notify()的例子,典型的生产者/消费者:


三、notifyAll()方法

唤醒等待对象内在锁的所有线程。

唤醒线程不会被执行,直到当前线程交出对象的内在锁。被唤醒的线程将和其它线程完全平等的竞争对象的同步块访问。


四、一个例子

public class MyProduct {private String name;private int productIdx;public MyProduct(String name, int productIdx) {this.name = name;this.productIdx = productIdx;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getProductIdx() {return productIdx;}public void setProductIdx(int productIdx) {this.productIdx = productIdx;}@Overridepublic String toString() {return "MyProduct [name=" + name + ", productIdx=" + productIdx + "]";}}

/** * 模拟工厂,制造产品放到产品队列中 *  * 会根据产品队列中产品数量来进行生产控制,当产品数大于MAX_PRODUCTS就停止生产 */public class FactoryThread implements Runnable {private Queue<MyProduct> productQueue;public static final int MAX_PRODUCTS = 10;public FactoryThread(Queue<MyProduct> productQueue) {this.productQueue = productQueue;}@Overridepublic void run() {if (null == productQueue) {return;}String threadName = Thread.currentThread().getName();int productIdx = 1; // 产品编号while (true) {if (produceProducts(threadName, productIdx)) {break;}productIdx++;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}private boolean produceProducts(String threadName, int productIdx) {boolean isInterrupted = false;synchronized (productQueue) {// 如果队列中产品太多,等待顾客消费后通知工厂while (productQueue.size() >= MAX_PRODUCTS) {try {productQueue.wait();} catch (InterruptedException e) {isInterrupted = true;break;}}if (!isInterrupted) {MyProduct p = new MyProduct(threadName, productIdx);productQueue.offer(p);System.out.println("FactoryThread: " + p + "; productQueue.size(): " + productQueue.size());productQueue.notifyAll();}}return isInterrupted;}}

/** * 模拟客户,从产品队列中得到产品 *  * 会根据产品队列中产品数量来进行消费控制,当产品数小于MIN_PRODUCTS就等待生产 *  */public class CustomerThread implements Runnable {private Queue<MyProduct> productQueue;public static final int MIN_PRODUCTS = 5;public CustomerThread(Queue<MyProduct> productQueue) {this.productQueue = productQueue;}@Overridepublic void run() {if (null == productQueue) {return;}String threadName = Thread.currentThread().getName();while (true) {if (consumeProducts(threadName)) {break;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}private boolean consumeProducts(String threadName) {boolean isInterrupted = false;synchronized (productQueue) {// 如果队列中产品太少,等待工厂生产后通知顾客while (productQueue.size() <= MIN_PRODUCTS) {try {productQueue.wait();} catch (InterruptedException e) {isInterrupted = true;break;}}if (!isInterrupted) {MyProduct p = productQueue.poll();System.out.println("CustomerThread: " + threadName + " buy " + p + "; productQueue.size(): "+ productQueue.size());productQueue.notifyAll();}}return isInterrupted;}}

import java.util.concurrent.ConcurrentLinkedQueue;public class Test {public static void main(String[] args) throws InterruptedException {ConcurrentLinkedQueue<MyProduct> productQueue = new ConcurrentLinkedQueue<MyProduct>();for (int i = 0; i < 5; i++) {new Thread(new FactoryThread(productQueue)).start();}for (int i = 0; i < 5; i++) {new Thread(new CustomerThread(productQueue)).start();}}}

再来看join,在sdk的Tread类里有如下代码:

    /**     * Waits for this thread to die.      *     * @exception  InterruptedException if any thread has interrupted     *             the current thread.  The <i>interrupted status</i> of the     *             current thread is cleared when this exception is thrown.     */    public final void join() throws InterruptedException {join(0);    }    /**     * Waits at most <code>millis</code> milliseconds for this thread to      * die. A timeout of <code>0</code> means to wait forever.      *     * @param      millis   the time to wait in milliseconds.     * @exception  InterruptedException if any thread has interrupted     *             the current thread.  The <i>interrupted status</i> of the     *             current thread is cleared when this exception is thrown.     */    public final synchronized void join(long millis)     throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {    while (isAlive()) {wait(0);    }} else {    while (isAlive()) {long delay = millis - now;if (delay <= 0) {    break;}wait(delay);now = System.currentTimeMillis() - base;    }}    }    /**     * Waits at most <code>millis</code> milliseconds plus      * <code>nanos</code> nanoseconds for this thread to die.      *     * @param      millis   the time to wait in milliseconds.     * @param      nanos    0-999999 additional nanoseconds to wait.     * @exception  IllegalArgumentException  if the value of millis is negative     *               the value of nanos is not in the range 0-999999.     * @exception  InterruptedException if any thread has interrupted     *             the current thread.  The <i>interrupted status</i> of the     *             current thread is cleared when this exception is thrown.     */    public final synchronized void join(long millis, int nanos)     throws InterruptedException {if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {    millis++;}join(millis);    }

代码说明了几点:

1、判断一个线程是否还活着,用的是isAlive()方法,并且这个方法调用后,所在线程处于阻塞中;

2、有三种方式可以让join从阻塞中跳出:

      a. 所等待的线程正常执行完毕;

      b. 所等待的线程被中断(Interrupt);

      c. 设置的等待时间已过;

这边有一个简单的例子:

子线程

import java.util.concurrent.atomic.AtomicInteger;public class MyThread implements Runnable {private AtomicInteger num;public MyThread(AtomicInteger num) {this.num = num;}@Overridepublic void run() {String threadName = Thread.currentThread().getName();for (int i = 0; i < 10; i++) {System.out.println(threadName + ": i = " + i + ", num = " + num.getAndDecrement());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
父线程

import java.util.concurrent.atomic.AtomicInteger;public class Test {public static void main(String[] args) {MyThread threadOne = new MyThread(new AtomicInteger(20));Thread t1 = new Thread(threadOne);t1.start();try {System.out.println("waiting for t1 end...");t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("all finished.");}}
如我们所需要的,父线程会一直等待子线程执行完毕后才会结束。


最后来看一下sleep,在sdk中是这样介绍的

    /**     * Causes the currently executing thread to sleep (temporarily cease      * execution) for the specified number of milliseconds, subject to      * the precision and accuracy of system timers and schedulers. The thread      * does not lose ownership of any monitors.     *     * @param      millis   the length of time to sleep in milliseconds.     * @exception  InterruptedException if any thread has interrupted     *             the current thread.  The <i>interrupted status</i> of the     *             current thread is cleared when this exception is thrown.     * @see        Object#notify()     */    public static native void sleep(long millis) throws InterruptedException;    /**     * Causes the currently executing thread to sleep (cease execution)      * for the specified number of milliseconds plus the specified number      * of nanoseconds, subject to the precision and accuracy of system      * timers and schedulers. The thread does not lose ownership of any      * monitors.     *     * @param      millis   the length of time to sleep in milliseconds.     * @param      nanos    0-999999 additional nanoseconds to sleep.     * @exception  IllegalArgumentException  if the value of millis is      *             negative or the value of nanos is not in the range      *             0-999999.     * @exception  InterruptedException if any thread has interrupted     *             the current thread.  The <i>interrupted status</i> of the     *             current thread is cleared when this exception is thrown.     * @see        Object#notify()     */    public static void sleep(long millis, int nanos)     throws InterruptedException {if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {    millis++;}sleep(millis);    }
有几个要点:

1、sleep()只是临时停止执行("temporarily cease execution"),并不是

2、"The thread does not lose ownership of any monitors",所以锁什么的都不会释放,相比较wait()方法会释放锁;

3、sleep()也会抛出异常InterruptedException;


这篇介绍了好几个方法都是阻塞方法,也都涉及到了InterruptedException,具体可以看几篇文章

InterruptedException的解读

Java 理论与实践: 处理 InterruptedException





0 0
原创粉丝点击