本章将会围绕下面几个问题展开分析:
线程是什么?
java中的线程状态有几种?
各个方法会对线程产生什么影响?
线程是一种轻量级进程,而进程又被称为正在进行的程序。所以线程就是程序代码run起来的样子。就像一个田径赛场的运动员,程序的运行长度就是它的跑道长度,刚刚开始跑的时候,精力充沛,跑道中间的时候,体力下降,遇到障碍物了,摔倒一跤…等等。
所以,跑起来的程序,也就是线程它在不同阶段可能会有不同的状态。
线程是一种操作系统层面的定义,并非java创造,Java作为一种程序语言,只是实现了线程的概念和操作,所以要深刻理解线程还需要去啃计算机操作系统理论。
在Thread源代码中,State是它的内部枚举,一共定义了六种状态:
NEW (就绪)
RUNABLE (运行)
BLOCKED (阻塞)
WAITING (等待)
TIMED_WAITING(限时等待)
TERMINATED**(结束)
这六种状态与计算机操作系统中定义的状态有些区别,一般来说,操作系统对一个线程的状态定义有六种:
起始
就绪
运行
等待
挂起
结束
为什么Java中定义的六种状态和操作系统的定义的状态不匹配呢?
因为它们的角度不一样。在操作系统中,状态的定义是围绕是否在CPU上运行,java中的定义是是否在JVM上运行。所以造成了两种状态定义的差异。
这种差异带来的不同主要是就绪、运行和等待三个主要状态。
因为,进入JVM后,线程状态是RUNABLE,但是在JVM中不代表在CPU上运行了,也可能JVM才把线程交到操作系统的排队队列中,也有可能运行了一段后被CPU切换了,重新进入了就绪等待下一次时间片。不论这个线程在操作系统中经过了多少次就绪、运行互相转换,这部分统统被JVM封装在RUNABLE状态中。
另外一点,操作系统中只有等待状态,当一个线程在CPU上运行到以某一刻,需要获取外部设备或其他事件时,为了提高计算机CPU的利用效率,CPU会让该线程先在一旁等待。这个动作称为运行状态到等待状态的转换。线程进入等待后,操作系统就没有再细化区分了,但是Java做了区分,它根据等待的不同时间,又分为了:
BLOCKED (阻塞)
WAITING (等待)
TIMED_WAITING(限时等待)
这三个等待对java来说有什么区别呢?
我的理解是主要针对java的设计产生了许多使用场景来区别的。如synchronized关键字,sleep方法、wait方法等等。
阻塞
它属于等待状态,它产生的场景一般在多线程环境中使用synchronized关键字,当一个线程没有抢到锁时,就会进入等待状态,此时,java叫它BLOCKED。
这种等待是不确定的,如果占有锁的线程一直不释放锁,那么阻塞的线程就会一直等待。直到锁被释放后,阻塞的状态才会结束,进入就绪状态重新竞争锁。
等待
它属于等待状态,它产生的场景一般在执行了wait()方法后,需要其他线程调用notify 或 notifyAll 方法才能解除它等待状态。这种状态Java叫他WAITING。
限时等待
它属于等待状态,它的等待是一种有限的等待,是有时间限制的。一般产生于调用sleep(long time)方法中。它状态的解除不需要其他线程来参与。这种等待,Java叫它TIMED_WAITING。
那么具体这六个状态怎么产生的,怎么相互装换的,具体的理解在下面的方法介绍中。
private volatile String name; // 线程名称
private int priority; // 线程优先级
private boolean daemon = false; // 是否是守护线程
private Runnable target; // 执行的任务
private ThreadGroup group; // 所在的线程组
private ClassLoader contextClassLoader; // 上下文类加载器
private AccessControlContext inheritedAccessControlContext;
ThreadLocal.ThreadLocalMap threadLocals = null; // 内部挂载的map容器
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 内部挂载的父子线程传递的map容器
private volatile int threadStatus = 0; // 用于判断线程是否启动
private volatile Interruptible blocker;
private long tid;
private volatile Interruptible blocker;
private final Object blockerLock = new Object(); // 线程内部的锁
Thread线程的创建共有九种方式:
一、public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
二、public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
三、Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
四、public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
五、public Thread(String name) {
init(null, null, name, 0);
}
六、public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
七、public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
八、public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
九、public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
从上可以看出,不论有多少种创建方式,这都基于其调用的init初始化方法。所以,了解了init初始化方法,对线程的创建就知道大概了。
/**
* 在注入参数中,分别是当前线程组、执行任务目标、线程名字、程序栈大小、类加载器、
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread(); //初始化父引用: 新线程的父引用指向创建者
SecurityManager security = System.getSecurityManager();
// 如果创建线程时没有指明所属线程组,则按下列进行初始化
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup(); // 将新线程归入到父线程的组
}
}
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted(); // 线程组中未启动线程数
this.group = g;
this.daemon = parent.isDaemon(); // 继承父线程的守护状态
this.priority = parent.getPriority(); // 继承父线程的优先级
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader; // 继承父线程的加载器
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 下方表示:线程中的inheritableThreadLocals指向的集合可以在父子线程之间传递。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 指定栈大小
this.stackSize = stackSize;
// 获取线程唯一编号
tid = nextThreadID();
}
如果对线程的初始化很懵,虽然我分析得也不是很深入,但是我还是得画个图出来大概解释一下,为什么会这样初始化。
还有一点,在线程中没有明确的“父子线程”这个结构,这个描述只是基于线程初始化中的如下代码:
Thread parent = currentThread(); // 指定创建线程为父线程
...
this.daemon = parent.isDaemon(); // 继承父线程的守护状态
this.priority = parent.getPriority(); // 继承父线程的优先级
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader; // 继承父线程的加载器
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
它说明每个线程都是被赋予了创建者线程的一些结构和数据,但是他们并没有继承关系。
每一个线程组都与它的父组关联,是一个典型的树状结构。
基于这样的线程父子继承关系,每一个线程也与它的直接父线程关联,ThreadLocal的设计,实现了线程内的变量传递,但是无法实现线程之间的变量传递,因此基于父子线程关系,设计出了inheritableThreadLocals 这个变量用来满足父子线程间的变量传递,这个传递在线程初始化时进行。
但是具体是只传递了Map结构,还是结构及数据都传递了呢?
根据初始化代码继续向下:
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
这里调用了ThreadLocalMap的构造器
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table; // 传递父对象的结构
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) { // 遍历父对象的每个结点
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
从上面可以看出,inheritableThreadLocals 这个变量实现了父子线程间的map拷贝,从而实现了线程之间的数据传递。
1. currentThread()
/**
* 这是一个静态本地方法,返回当前线程对象。
*
public static native Thread currentThread();
2. yield
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint. 这个方法是告诉cpu的调度,当前线程愿意放弃对cpu的使用权。
*
* Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
* 这个方法不一定有用,主要用于改善线程之间的相对进度,谁更快谁更慢。
* 使用这个方法应该和实际业务设计结合起来,才能发挥它的作用。
*
*
It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
* 这个方法可能对重复错误出现时进行调试测试有用,也可能对并发控制设计有用。
*
*/
public static native void yield();
线程让步,最近也在看相关的书籍和资料,这个方法总的来说,是让当前线程放弃对CPU的使用权,重新处于一种对cpu的平等竞争状态,但是再次获得cpu的线程可能还是它,也可能不是。因为平时用这个方法的经验不是很多,个人理解这个方法能相对减缓线程之间的相对进度。
3. sleep(long millis)
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*
public static native void sleep(long millis) throws InterruptedException;
这个方法是平时常用的方法,它会让当前线程进入TIMED_WAITING状态,而不是WAITING状态。TIMED_WAITING是限时等待,自己可以苏醒;WAITING 是一直等待,需要其他线程唤醒,对于调用sleep(long millis)方法的线程来说,线程的苏醒自己就可以完成,因此是进入TIMED_WAITING。
不过这个方法的有意思的地方不在这里,而是InterruptedException。通过官方的解释可以理解到:
如果当前线程在睡眠中被其他线程中止了,就会抛出InterruptedException异常,并且当前线程中断标志被清除。
意思就是,当前线程的初始中断标志位false,其他线程调用了interrupt方法后,当前线程的中断标志变为了true,但是如果正好当前线程是在睡眠中被中断的,那么抛出异常,并且中断标志为回到false。
4. sleep(long millis, int nanos)
这个方法和sleep(long millis) 作用一致,不同的是它多了一个纳秒单位。可以更精细的控制时间。
5. start
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the run
method of this thread.
* 这个线程一旦开始,JVM就会调用这个线程的run方法。
*
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start
method) and the other thread (which executes its
* run
method).
* 意思就是这是两个线程在运行,一个是当前线程,一个是JVM的调用线程。
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
* 线程只能启动一次,多次启动将会抛出异常。
public synchronized void start() {
/**
* threadStatus 是线程初始的标志,默认为0,线程一旦启动,threadStatus 也会改变。因此线程不可以多次启动。
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this); // 初始化的线程组中,添加这个启动的线程。这与初始化的g.addUnstarted()不同,g.addUnstarted()是增加线程组中未启动的线程个数,而这个是添加线程对象,添加时如果涉及扩容,threadGroup的数组按原容量的2倍扩容。
boolean started = false;
try {
start0(); 这是一个本地方法,只有它调用成功,才能代表这个线程确实启动成功了,所以有started前后判断控制。
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this); // 如果启动失败,线程组则处理当前线程。
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
对于start方法而言,它能让线程从NEW状态进入RUNNABLE状态,因为这个方法可以启动线程。
7. run()
run方法是一个任务容器,由JVM调用执行。
@Override
public void run() {
if (target != null) {
target.run();
}
}
在原生的Thread中,run方法是一个平台,是一辆空卡车,没有执行目标。因此在创建原生Thread线程时,都需要注入一个Runable任务。由Runable接口的run方法来具体描述目标任务。否则什么都不会执行。
如果自定义一个线程MyThread继承了Thread,通常可以重写Thread的run方法,直接在run里实现细节,这样JVM在调用run时,就能执行到具体细节。而不需要注入Runable任务。
run方法不会启动一个线程,它仅仅是一个方法而已。由JVM调用。
run方法是无法返回值的,这取决于返回描述为void。但是callable可以接受返回值。callable属于并发包里面的类,这个会在接下来专门的篇章中分析。这里不再赘述。
8. resume(过时)
这个方法已被弃用,所以这里只做简单的概括。它可以重启线程。
9. suspend(过时)
这个也是过时的方法,它可以暂停线程,但是只会释放cpu不会释放锁,因此容易造成死锁。
10. stop(过时)
这个也是过时的方法,它会强制停止线程,容易造成线程不安全的情况。
11. interrupt、interrupted、 isInterrupted
这里分析一下这三兄弟。
interrupt
interrupt 是一个成员方法,意味着谁拿到了这个线程对象,都可以调用这个方法,包括自己。因此会存在并发问题。调用此方法将会在这个线程对象上打上一个中断标志,而线程依然会继续运行。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) { // 这里处理并发
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag,调用了这个方法的线程将会被打上中断标志。
b.interrupt(this);
return;
}
}
interrupt0();
}
对于中断行为,可以分为好几个场景:
(1). NEW状态下中断
通过测试,NEW状态下,线程未启动。中断不会生效。
(2). RUNNABLE状态下中断
RUNABLE下,中断操作会使线程打上中断标志位。
(3). 阻塞状态(WAITING、TIMED_WAITING 、BLOCKED)下中断
阻塞状态下,如果被中断则会退出阻塞状态,并抛出InterruptedException异常,抛出异常后,中断标志将被清除。这一点从常用的几个方法上就可以看出端倪:
public final void wait() throws InterruptedException{...};
public static native void sleep(long millis) throws InterruptedException;
public final void join() throws InterruptedException{...};
interrupted
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
这是一个静态方法,从方法体中可以看出,这个方法只能对自己执行。用于判断当前线程的中断标志位是ture还是false。
isInterrupted()
public boolean isInterrupted() {
return this.isInterrupted(false);
}
这是一个成员方法,与interrupted相同之处在于,其都是调用的isInterrupted(boolean ClearInterrupted)方法。然而不同之处在于一个为参数true,一个参数为false。
这里的玄机在于boolean ClearInterrupted这个参数的意思为,是否清除中断标志位。对于interrupted()静态方法来说,调用它会清除当前线程的中断标志位,重置为false。而isInterrupted()不会清除。所以考虑使用成员方法isInterrupted()还是静态方法interrupted(),主要取决于是否想清除中断标志位。
14. join
This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
译文:这个方法是用一个循环的wait来实现,线程结束时将会执行这个线程对象的notifyAll()方法,
释放这个对象监视器中的所有等待线程。所以在这个线程对象上,最好就不要再使用wait和notify、notifyAll等方法了。
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) { // 如果join时间为0,则表示进入WAITING状态,。
while (isAlive()) { // 外面为什么需要加循环? 可以参考wait方法调用说明,防止虚假唤醒。
wait(0); // 调用了wait(0)方法来实现。
}
} else {
while (isAlive()) { // 这里是同理
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay); // 进入TIMED_WAITING状态。
now = System.currentTimeMillis() - base;
}
}
}
join方法的底层其实就是使用wait方法来实现。对于执行任何线程t1.join方法的线程t2而言,该线程t2执行到t1.join处,将会调用wait()从而进入t1对象的监视器中,t2也因此由RUNNABLE状态转为WAITTING状态,直到等到t1线程对象结束时调用notifyAll方法,t2线程才会被释放,由WAITTING回到RUNNABLE状态。
所以,join方法可以实现线程间的执行排序,决定谁先执行谁后执行。
15. wait()
wait方法源于Object对象,线程作为一个对象也同样继承了这些方法。
对于wait方法,可以详细阅读官方注释:
/**
* 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)}.
* 当前线程进入wait 直到其他线程调用notify或notifyAll唤醒,这个方法相当于调用了 wait(0),wait(0)是永久等待的意思,直到被其他线程唤醒;
*
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until 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. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
* 当前线程必须拥有这个对象的监视器。调用后会一直等待直到其他线程通过notify或notifyAll唤醒。
* 唤醒后,当前线程将会进入锁池,重新竞争锁以获取运行权利。
*
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* This method should only be called by a thread that is the owner
* of this object's monitor.
* 单参数版本中,有可能会造成中断和虚假唤醒,所以调用这个方法必须在循环中进行。
* 线程必须拥有这个对象的监视器才能调用这个对象的wait方法,
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor. // 未拥有监视器非法调用异常
* @throws InterruptedException if any thread interrupted the // 阻塞情况下,被其他线程中断异常。
* current thread before or while the current thread
* was waiting for a notification. The interrupted
* status of the current thread is cleared when
* this exception is thrown.
*/
public final void wait() throws InterruptedException {
wait(0);
}
从方法体中可以看出实际上是调用的wait(long timeout)方法;
因此我们看一下wait(long timeout)方法。
16.wait(long timeout)、notify() 和 notifyAll()
* This method causes the current thread (call it T) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object. Thread T
* becomes disabled for thread scheduling purposes and lies dormant
* until one of four things happens:
* 调用线程将会进入这个对象监视器中的等待集区域,并且释放对象监视器。
* 该线程将会处于禁用和休眠状态,除非以下四件事情之一发生:
*
*
* - Some other thread invokes the {@code notify} method for this
* object and thread T happens to be arbitrarily chosen as
* the thread to be awakened.
* 其他线程调用这个对象监视器的notify方法来随机唤醒等待池中的某个线程。
*
*
- Some other thread invokes the {@code notifyAll} method for this
* object.
* 其他线程调用这个对象监视器的notify方法来随机唤醒等待池中的所有线程。
*
*
- Some other thread {@linkplain Thread#interrupt() interrupts}
* thread T.
* 其他线程调用这个线程的interrupt方法来中断线程。
*
*
- The specified amount of real time has elapsed, more or less. If
* {@code timeout} is zero, however, then real time is not taken into
* consideration and the thread simply waits until notified.
* 入参时间结束将会被唤醒,如果参数为0,则代表一直休眠。
*
* The thread T 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 {@code wait}
* method was invoked. Thread T 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.
* 如果线程被唤醒,则从对象等待池中删掉这个线程,并进入锁池与其他线程重新竞争。
* 如果重新竞争成功,这个线程将会从之前调用wait方法的位置继续向下执行,而不是代码最开始的地方。
*
*
* A thread can also wake up without being notified, interrupted, or
* timing out, a so-called spurious wakeup. 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:
* 等待池的线程也可能不在上述四种情况下被唤醒,也就是所谓的“虚假唤醒”。
* 所以调用wait时应该在一个循环代码块中去执行,像下方例子一样:
*
*
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
*
*
* 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.
* 调用interrupt方法会唤醒该线程并抛出异常。但是在恢复此线程锁定状态前,不会抛出异常。
* 意思就是,如果采用intterrput唤醒线程,线程会进入到锁池里和其他线程重新竞争锁,不管这个过程有多久,
* 也必须等到它竞争到锁,回到synchronized代码块后,才会抛出 InterruptedException异常。
*
*
* 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.
* 注意调用wait方法只会解除当前对象的锁,不会解除其他对象的锁。
* 意思就是当有多重锁时,比如锁1和锁2为包含关系,锁2解除了,但还是在锁1内。
*
上述提到了几个点:
(1) 对象监视器
网上已经有很多关于wait方法的描述和解释。这里我借鉴一些在此描述:
每个对象都只有一个对象监视器,对象监视器相当于一个建筑,建筑中有一个茅坑。
线程相当于上厕所的人,在多线程的情况下,相当于许多人都要去使用这个对象的茅坑,存在竞争关系。
怎么办?
于是在建筑内有一个锁池的概念,设置了一个锁池,锁池区域长100米,宽无边际,所有人都在锁池区域的起点开始百米赛跑。
谁先跑到终点抢到茅坑,谁就先上厕所。
于是画面妙不可言...
最后的结果,当然是谁抢占了茅坑,谁就上厕所。其他没有抢到茅坑的回到锁池起点,等待茅坑空出来后再重新抢。
现在假如有一个人抢到进去了,开始使用茅坑。这个意思就如进入同步代码块中开始执行。
如果这个人上到一半,被调用了wait怎么办?
这个人就会被送入等待池中,等待池也是建筑中的一个概念,相当于休息区。
当这个人被送入等待池后,茅坑就空出来了。这也就是调用wait方法,会释放锁的原因。茅坑空出来后,其他锁池中的对象又会开始竞争茅坑了。
那等待池中的人怎么办?
wait的官方注释说了,只有四种情况会唤醒该线程:
(1)nofity (对于这个对象监视器中等待区的所有线程,随机唤醒,不一定能唤醒指定线程)
(2)notifyAll (对于这个对象监视器中等待区的所有线程,全部唤醒)
(3)interrupt (中断异常必须等线程回到同步代码块中才能抛出)
(4)超时时间到(如果超时时间为0,则代表永远睡眠,不会自动唤醒)
被唤醒后怎么办?
唤醒后的线程会重新进入锁池,继续竞争茅坑。锁竞争成功后,继续从之前的调用wait的位置向下执行代码,而不是从代码第一行。
(2) 虚假唤醒
官方还说了,除了上述四种情况外可能唤醒线程,还有一种虚假唤醒的可能。
什么情况下会产生虚假唤醒?