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

我们先来看一下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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值