线程系列目录
- Thread线程从零认识到深层理解——初识
- Thread线程从零认识到深层理解——六大状态
- Thread线程从零认识到深层理解——wait()与notify()
- Thread线程从零认识到深层理解——生产者/消费者模型
- Thread线程从零认识到深层理解——线程安全
- 线程池从零认识到深层理解——初识
- 线程池从零认识到深层理解——进阶
注意:本系列博文源码分析取自于Android SDK=30,与网络上的一些源码可能不一样,可能他们分析的源码更旧,无需大惊小怪。
前言
首先Object类中有五个方法wait(long timeout, int nanos)、wait(long timeout)、wait()、notify()、notifyAll(),几种方法关系内部锁与线程的等待和唤醒有关,执行上面5种方法的线程必须拥有对象控制权monitor。线程可通过synchronized代码块或方法来获得对象的控制权。
monitor
monitor翻译是监视器的意思,对象monitor即对象控制权,也就是我们常说的对象锁。在Java中,对象的控制权在同一时刻只能被一个线程所拥有。
在JVM中会为每个使用(synchronized)的对象维护两个集合,即每个锁都有两个队列,同步队列和等待队列,有的人也叫锁池和等待池。
- 同步队列:用于存储获取锁失败的线程,他们将一起竞争同一对象的锁
- 等待队列:用于存储调用了wait之一方法的线程。当等待队列中的线程被唤醒,就会被移到同步队列中
一、 等待
wait等待方法有三种如下,调用wait方法的当前线程会释放获得的对象锁,同时线程会挂起进入锁对象的等待队列中。当持有对象锁的其他线程调用了notify或notifyAll方法会将等待线程唤醒,线程加入到锁对象的同步队列中。一旦线程再次获得对象锁,线程会恢复到wait调用前状态并继续执行代码。
Object.wait()方法调用后会释放对象锁,而线程的Thread.sleep()方法不会让出对象锁,只会让出CPU时间片。两者都会让线程进入等待状态
1. 三种等待方法
1. wait(long timeout, int nanos)
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the {@link java.lang.Object#notifyAll()} method for this object,
* or some other thread interrupts the current thread, or a certain amount of real time has elapsed.
* 使当前线程等待,直到另一个线程为此对象调用{@link java.lang.Object#notify()}方法
* 或{@link java.lang.Object#notifyAll()}方法,或某些其他线程中断当前线程,或者已经经过一定的实时时间。
* <p>
* This method is similar to the {@code wait} method of one
* argument, but it allows finer control over the amount of time to
* wait for a notification before giving up.
* 此方法类似于一个参数的{@code wait}方法,但在放弃之前它可以更好更精确地控制等待通知的时间
* <p>
* 实时量(以纳秒为单位)由下式给出:1000000*timeout+nanos
* <p>
* In all other respects, this method does the same thing as the method {@link #wait(long)} of
* one argument.In particular,{@code wait(0, 0)} means the same thing as {@code wait(0)}.
* 在所有其他方面,此方法与一个参数的方法{@link #wait(long)}的作用相同。特别是{@code wait(0,0)}的含义与{@code wait(0)}相同
* <p>
* <p>
* The current thread must own this object's monitor.
* The thread releases ownership of this monitor and waits until either of the
* following two conditions has occurred:
* 当前线程必须拥有object的锁。线程释放此锁的所有权,并等待直到以下两个条件中的任何一个发生:
* <li>Another thread notifies threads waiting on this object's monitor
* to wake up either through a call to the {@code notify} method
* or the {@code notifyAll} method.
* <p>
* 1.另一个线程通过调用{@code notify}方法或{@code notifyAll}方法来通知等待在该对象的监视器上的线程唤醒。
* <li>The timeout period, specified by {@code timeout}
* milliseconds plus {@code nanos} nanoseconds arguments, has elapsed.
* 2.由{@code timeout} 毫秒加上{@code nanos}纳秒参数指定的超时时间已经过去。
* The thread then waits until it can re-obtain ownership of the
* monitor and resumes execution.
* 然后,线程等待,直到它可以重新获得监视器的所有权并恢复执行。
* <p>
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* 与一个参数版本一样,可能会产生中断和虚假唤醒,并且此方法应始终在循环中使用:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait(timeout, nanos);
* ... // 执行适合条件的行动
* }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
* 此方法只能由作为该对象的监视器的所有者的线程调用。
*
* @param timeout 等待的最长时间(以毫秒为单位)
* @param nanos 附加时间,以纳秒为单位 0-999999.
* @throws IllegalArgumentException 如果timeout的值是负数或nanos的值不在0-999999范围内。
* @throws IllegalMonitorStateException 如果当前线程不是此object的监视器的所有者。
* @throws InterruptedException 如果在当前线程正在等待通知之前或之时有任何线程中断了当前线程。
* 抛出此异常后,将清除当前线程的中断状态。
*/
@FastNative
public final native void wait(long timeout, int nanos) throws InterruptedException;
通过进行源码的注释翻译理解得出如下结论:
![](/qrcode.jpg)
- 首先wait(long timeout, int nanos)是一个native方法
- 与后面讲解的方法wait(long timeout)不一样的地方是超时时间可以精确到纳秒级,超时时间为1000000*timeout+nanos,除了超时时间的更加精准化,其他地方与wait(long timeout)和wait()并无差异。
- 调用此方法后会使线程处于等待状态进入等待队列,当被其他线程唤醒、自身等待超时或者被其他线程中断时会结束等待状态。
- 参数除了要符合要求,调用次方法的线程必须拥有对象monitor ,否则会抛IllegalMonitorStateException
- 等待时间超过timeOut后,线程将退出等待状态,进入同步队列进入BLOCKED状态等待对象锁的获取。
2. wait(long timeout)
wait(long timeout)相比于wait(long timeout, int nanos)其他都一样。它最终也是调用了wait( timeout, 0)。默认参数nanos=0,等待时间精度只能在毫秒级。
/**
* <p>
* 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;
* 然后从该对象的等待集中删除线程T并重新启用线程调度。
* 然后,它以通常的方式与其他线程竞争在对象上进行同步的权利。
* <p>
* 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 {@code wait}
* method was invoked.
* 一旦它获得了对象的控制权,它对对象的所有同步声明都将恢复到原样-即,调用{@code wait} *方法时的情况。
* <p>
* Thread <var>T</var> then returns from the
* invocation of the {@code wait} method. Thus, on return from the
* {@code wait} method, the synchronization state of the object and of
* thread {@code T} is exactly as it was when the {@code wait} method
* was invoked.
* 线程<var> T </ var>然后从{@code wait}方法的调用中返回。因此,从* {@code wait}方法返回时,
* 对象和*线程{@code T}的同步状态与调用{@code wait}方法*时的状态完全相同。
* <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.
* 线程也可以唤醒,而不会被通知,中断或*超时,即所谓的<i>虚假唤醒</ i>。尽管在实践中很少会发生这种情况,
* 但是应用程序必须通过测试可能导致线程唤醒的条件来防范它,并在条件不满足时继续等待。
* <p>
* 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>
*
* <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
* interrupted} by any thread before or while it is waiting, then an
* {@code InterruptedException} is thrown. This exception is not
* thrown until the lock status of this object has been restored as
* described above.
* 如果当前线程在等待之前或等待时被任何线程{@linkplain java.lang.Thread#interrupt()*打断},
* 则将引发* {@code InterruptedException}。在如上所述恢复该对象的锁定状态之前,不会引发此异常。
*
* <p>
* Note that the {@code wait} 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>
* 请注意,{@code wait}方法将当前线程放入此对象的等待集中,
* 因此仅解锁该对象;当前线程所处的任何其他对象可能在线程等待时保持同步锁定。
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
* 此方法只能由作为该对象的监视器的所有者的线程调用。
*
* @param timeout 等待的最长时间(以毫秒为单位)
* @throws IllegalArgumentException 如果timeout的值是负数或nanos的值不在0-999999范围内。
* @throws IllegalMonitorStateException 如果当前线程不是此object的监视器的所有者。
* @throws InterruptedException 如果在当前线程正在等待通知之前或之时有任何线程中断了当前线程。
* 抛出此异常后,将清除当前线程的中断状态。
*/
public final void wait(long timeout) throws InterruptedException {
wait(timeout, 0);
}
3. wait()
wait()等价于wait(0,0)方法,其他方面与 wait(long timeout)和wait(long timeout, int nanos)一致,只是该方法会一直让线程处于WAITING状态,线程不会超时自动唤醒,除非被其他线程唤醒或者中断。
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* 使当前线程等待,直到另一个线程为此对象调用* {@link java.lang.Object#notify()}方法或
* {@link java.lang.Object#notifyAll()}方法。
* 换句话说,此方法的行为就像它简单地执行调用{@code wait(0)}一样。
*
* <p>
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait();
* ... // Perform action appropriate to condition
* }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @throws IllegalArgumentException 如果timeout的值是负数或nanos的值不在0-999999范围内。
* @throws IllegalMonitorStateException 如果当前线程不是此object的监视器的所有者。
* @throws InterruptedException 如果在当前线程正在等待通知之前或之时有任何线程中断了当前线程。
* 抛出此异常后,将清除当前线程的中断状态。
*/
public final void wait() throws InterruptedException {
wait(0);
}
2. wait与线程状态
关于线程的几种状态,如果各位看官不太熟悉,请阅读我的另一篇博文《Thread线程从零认识到深层理解——六大状态》。
wait()和wait(long timeout)方法最终都是调用了wait(long timeout, int nanos),实际设置的超时时间time=timeout*1000000+nanos。
- time>0,则线程进入TIMED_TIME状态,超时自动唤醒,也可被其他线程唤醒或者中断
- time=0,则线程进入WAITING状态,一直等待,除非被其他线程唤醒或者中断
- time<0, 线程会抛出IllegalArgumentException异常。
static class WaitTestThread extends Thread {
long timeOut;
public WaitTestThread(long timeOut) {
this.timeOut = timeOut;
}
@Override
public void run() {
// System.out.println("synchronized before 状态: 线程ID=" + Thread.currentThread().getId() + " 线程状态:" + Thread.currentThread().getState());
// System.out.println("synchronized before 状态:" + currentThread().getId() + " Name =" + currentThread().getName());
synchronized (Test.class) {
System.out.println("****************wait before 状态: 线程ID=" + Thread.currentThread().getId() + " 线程状态:" + Thread.currentThread().getState());
try {
Test.class.wait(timeOut);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("***************wait after 状态: 线程ID=" + Thread.currentThread().getId() + " 线程状态:" + Thread.currentThread().getState());
}
}
}
1. wait()与WAITING
public static void main(String[] args) {
waitThread = new WaitTestThread(0);
boolean isStarted = false;
long currentTime = System.currentTimeMillis();
while (true) {
System.out.println("waitThread 状态1: 线程状态:" + waitThread.getState());
if (!isStarted) {
waitThread.start();
isStarted = true;
}
if (waitThread.getState().equals(Thread.State.TERMINATED) || System.currentTimeMillis() - currentTime > 15) {
return;
}
}
}
运行结果:
waitThread 状态1: 线程状态:NEW
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:RUNNABLE
...
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:BLOCKED
waitThread 状态1: 线程状态:BLOCKED
waitThread 状态1: 线程状态:BLOCKED
****************wait before 状态: 线程ID=11 线程状态:RUNNABLE
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:WAITING
waitThread 状态1: 线程状态:WAITING
waitThread 状态1: 线程状态:WAITING
...
根据结果分析:
- 当线程创建New后,线程状态进入NEW状态,当掉用start()方法后,线程进入RUNNABLE状态
- 当代码执行到synchronized (Test.class )时申请对象锁,初始状态线程未拥有Test.class类锁,所以是BLOCKED状态,当获取到锁后,状态变为RUNNABLE状态。
- 接着代码执行 Test.class.wait(0),线程被挂起变为WAITING状态,直到唤醒或者中断操作改变此状态。
2. wait(long timeOut)与WAITING
public static void main(String[] args) {
waitThread = new WaitTestThread(12);
boolean isStarted = false;
long currentTime = System.currentTimeMillis();
while (true) {
System.out.println("waitThread 状态1: 线程状态:" + waitThread.getState());
if (!isStarted) {
waitThread.start();
isStarted = true;
}
if ( System.currentTimeMillis() - currentTime > 35) {
return;
}
}
}
运行结果:
waitThread 状态1: 线程状态:NEW
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:RUNNABLE
...
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:BLOCKED
...
waitThread 状态1: 线程状态:BLOCKED
****************wait before 状态: 线程ID=11 线程状态:RUNNABLE
waitThread 状态1: 线程状态:RUNNABLE
...
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:TIMED_WAITING
waitThread 状态1: 线程状态:TIMED_WAITING
...
waitThread 状态1: 线程状态:TIMED_WAITING
//标签 1 START
waitThread 状态1: 线程状态:RUNNABLE
...
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:BLOCKED
...
waitThread 状态1: 线程状态:BLOCKED
//标签 1 END
waitThread 状态1: 线程状态:RUNNABLE
...
waitThread 状态1: 线程状态:RUNNABLE
***************wait after 状态: 线程ID=11 线程状态:RUNNABLE
waitThread 状态1: 线程状态:RUNNABLE
waitThread 状态1: 线程状态:TERMINATED
waitThread 状态1: 线程状态:TERMINATED
...
根据结果分析:
- 当线程创建New后,线程状态进入NEW状态,当掉用start()方法后,线程进入RUNNABLE状态
- 当代码执行到synchronized (Test.class )时申请对象锁,初始状态线程未拥有Test.class类锁,所以是BLOCKED状态,当获取到锁后,状态变为RUNNABLE状态。
- 接着代码执行 Test.class.wait(12),线程被挂起变为TERMINATED状态,知道某种操作改变此状态。
- 在标签1处有一个有趣的地方需要额外注意,当线程结束TERMINATED转态,线程会先转为RUNNABLE状态。接着BLOCKED>>RUNNABLE的状态改变是因为线程再次获得对象锁。其拿到锁的过程操作中会有一个申请时状态BLOCKED,直到最终拿到对象锁,线程状态又变为了RUNNABLE状态。
- 线程从挂起中退出后,重新获得对象锁,从挂起的地方继续执行代码,直到run()方法中代码跑完,线程状态变为TERMINATED
二、唤醒
Object的notify()和notifyAll()都称为唤醒方法,只能唤醒因调用wait挂起的线程,notify()随机唤醒一个线程,notifyAll()唤醒挂起的全部线程,被唤醒的线程从锁对象的等待队列进入同步队列。
调用锁对象的notify()和notifyAll()方法的线程必须拥有该对象的锁,否则抛异常线程想要拥有锁对象的控制权可以通过Synchronized代码块或方法拥有。线程调用唤醒方法后并不会立马释放锁,而是会等待同步方法或者同步代码块中代码执行完毕才会释放对象锁。
1. notify()
随机唤醒等待队列中的一个线程,选择机制由调度机制决定,与线程优先级无关。
/**
* 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.
* <p>
* 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} methods.
* 唤醒正在此对象的监视器上等待的单个线程。如果有任何线程在等待该对象,则选择其中一个唤醒。
* 该选择是任意的,并且在实现时自行决定。线程通过调用{@code wait}方法之一在对象的监视器上等待。
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object.
* 在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。
* <p>
* 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>
* 1.通过执行该对象的同步实例方法
* 2.通过执行在对象上同步的{@code Synchronized}语句的主体
* 3. 对于类型为{@code Class}的对象,请执行该类的同步静态方法。
* </ul>
* <p>
* 对于同一对象,一次只能有一个线程拥有该对象的锁,其他线处于等待队列中。
*
* @throws IllegalMonitorStateException 如果当前线程不是此对象的监视器的所有者。
*/
@FastNative
public final native void notify();
2. notifyAll()
唤醒锁对象等待队列中的所有线程。
/**
* 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} methods.
* 唤醒正在此对象的监视器上等待的所有线程。线程通过调用{@code wait}方法之一在对象的监视器上等待。
* <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} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
* 此方法只能由作为该对象的监视器的所有者的线程调用。
* 有关线程可以成为监视器所有者的方式的说明,请参见{@code notify}方法。
*
* @throws IllegalMonitorStateException 如果当前线程不是此对象的监视器的所有者。
*/
@FastNative
public final native void notifyAll();
三、代码示例
1. notify与wait的互动
public static void main(String[] args) {
waitThread = new WaitThread(0);
waitThread.start();
System.out.println("waitThread 状态1: 线程ID=" + waitThread.getId() + " 线程状态:" + waitThread.getState());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waitThread 状态2: 线程ID=" + waitThread.getId() + " 线程状态:" + waitThread.getState());
synchronized (Test.class) {
System.out.println("notify before 状态: 线程ID=" + waitThread.getId() + " 线程状态:" + waitThread.getState());
Test.class.notify();
System.out.println("notify after 状态: 线程ID=" + waitThread.getId() + " 线程状态:" + waitThread.getState());
}
}
static class WaitThread extends Thread {
long timeOut;
public WaitThread(long timeOut) {
this.timeOut = timeOut;
}
@Override
public void run() {
synchronized (Test.class) {
System.out.println("wait before 状态: 线程ID=" + Thread.currentThread().getId() + " 线程状态:" + Thread.currentThread().getState());
try {
Test.class.wait(timeOut);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait after 状态: 线程ID=" + Thread.currentThread().getId() + " 线程状态:" + Thread.currentThread().getState());
}
}
}
运行结果:
waitThread 状态1: 线程ID=11 线程状态:RUNNABLE
wait before 状态: 线程ID=11 线程状态:RUNNABLE
waitThread 状态2: 线程ID=11 线程状态:WAITING
notify before 状态: 线程ID=11 线程状态:WAITING
notify after 状态: 线程ID=11 线程状态:BLOCKED
wait after 状态: 线程ID=11 线程状态:RUNNABLE
根据运行打印信息得知,线程由WAITING状态被唤醒后,线程进入同步队列处于BLOCKED状态,直到争抢到对象锁,继续挂起之前的代码运行。
2. IllegalMonitorStateException
在实际运行中,线程获得锁的对象与调用wait或notify的对象不一致时,就会出现该种异常。
private static void test() {
Object object = new Object();
synchronized (Test.class) {
System.out.println("notify before");
object.notify();
System.out.println("notify after 状态" + Thread.currentThread().getState());
}
}
或者
private static void test() {
Object object = new Object();
synchronized (Test.class) {
System.out.println("notify before");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("notify after 状态" + Thread.currentThread().getState());
}
}
运行结果:
Exception in thread "main" java.lang.IllegalMonitorStateException
如果执行线程没有获得对象的控制权就调用wait和notify或notifyAll等方法就会抛出抛IllegalMonitorStateException异常。
总结
本篇博文主要通过分析wait与notify的源码以及代码案例演示,弄清楚线程的等待和唤醒机制与原理。
根据源码分析、代码运行测试可知如下结论:
- 调用对象的wait和notify或notifyAll方法,执行线程必须先获得该对象控制权,否则抛异常。线程可通过synchronized代码块或方法来获得对象的控制权。
- 在同步代码或同步方法中,拥有了对象锁才能进入RUNNABLE状态。经notify或者nofityAll唤醒的线程从等待队列退出,加入同步队列,此时线程会处于BLOCKED状态,直到再次获得对象锁进入RUNNABLE状态。
- 线程调用notify或notifyAll完毕后不会立即释放对象锁,而是会在synchronized方法或者代码块执行完毕后才会释放对象锁。
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
相关链接: