public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- NEW (初始): 新创建线程对象,但还没有调用 start() 方法
- RUNNABLE (运行): java 把 就绪 (ready) 和运行 (running) 一起归属于runnable。线程对象创建后,被其它的线程(如: main线程)调用start() 方法。该线程就处于就绪(ready)状态进入可运行线程池中,等待cpu线程调度。就绪状态的线程获得cpu时间片后变为 运行状态 (running)
- BLOCKED (阻塞) : 线程阻塞于锁
- WAITING(等待) : 线程等待其它线程唤醒
- TIMED_WAITING (超时等待) : 该状态不同于WAITING,它可以在指定的时间后自行返回。
- TERMINATED (终止) :该线程执行完毕
wait() notify() notifyAll() 都是 Object类的方法,使用这三个方法的前提是先获得该对象的锁。
- 调用 wait 方法后,释放持有的对象锁,线程状态有 Running 变为 Waiting,并将当前线程放置到对象的 等待队列;
- 调用notify 或者 notifyAll 方法后,等待线程依旧不会从 wait 返回,需要调用 noitfy 的线程释放锁之后,等待线程才有机会从 wait 返回;
- notify 方法:将等待队列的一个等待线程从等待队列种移到同步队列中 ,
- notifyAll 方法:将等待队列种所有的线程全部移到同步队列,被移动的线程状态由 Waiting 变为 Blocked。
等待队列(等待池) 同步队列(锁池)
同步队列(锁池):假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的同步队列(锁池)中,这些线程状态为Blocked。
等待队列(等待池):假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时 线程A就进入到了该对象的等待队列(等待池)中,此时线程A状态为Waiting。如果另外的一个线程调用了相同对象的notifyAll()方法,那么 处于该对象的等待池中的线程就会全部进入该对象的同步队列(锁池)中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么 仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的同步队列(锁池)。
深入join()
join() : 保证线程的执行顺序
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
MyThread1 t=new MyThread1();
t.start();
t.join();
System.out.println(Thread.currentThread().getName()+"运行");
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + "执行!");
}
}
}
一句话总结: 让获得这个线程锁(t1)的线程(main) 等待,t1 执行完再唤醒线程(main),这样就保证了执行顺序。
看完下面的分析就可以更好理解上面的总结
JDK中 join() 的源码
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;
}
}
}
join的实现就是调用wait()方法。
wait() 方法源码
public final native void wait(long timeout) throws InterruptedException;
该方法为本地方法,调用此方法的当前线程需要释放锁,并等待唤醒。在上述例子中,主线程调用子线程对象的join()方法,因此主线程在此位置需要释放锁,并进行等待。
main线程运行,运行到t.join(等待时间)时,相当于运行 t.wait(),main线程获得线程对象 t 的锁,调用wait(等待时间)方法,main线程又释放掉 线程对象t 的锁,从 running变为 blocked 进入等待队列,等待线程对象t唤醒。
那么join()方法在什么时候唤醒 main线程?
查看Thread类中存在exit()方法,源码如下:
private void exit() {
if (group != null) { //线程组在Thread初始化时创建,存有创建的子线程
group.threadTerminated(this); //调用threadTerminated()方法
group = null;
}
target = null;
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
exit()在线程执行完run()方法之后会被调用,此时线程组中存在当前子线程,因此会调用线程组的threadTerminated()方法。 查看ThreadGroup.threadTerminated()方法源码:
void threadTerminated(Thread t) {
synchronized (this) {
remove(t); //从线程组中删除此线程
if (nthreads == 0) { //当线程组中线程数为0时
notifyAll(); //唤醒所有待定中的线程
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
通过此方法,将子线程从线程组中删除,并唤醒其他等待的线程。在上述例子中,此时子线程被销毁,并释放占用的资源,并唤醒等待中的线程。而在join()方法中等待的主线程被唤醒,并获得锁,继续往下执行 t.join() 后面的代码。
参考资料:
https://juejin.cn/post/6844903624842149895#comment
https://juejin.cn/post/6844903558433734669#heading-13



