我们先来看一下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,具体可以看几篇文章
Java 理论与实践: 处理 InterruptedException