Java 中的线程有如下六种状态:
- 新建(New): 当线程对象被创建后,它就处于新建状态。在这个状态下,线程还未开始执行。
- 可运行(Runnable): 调用线程的
start()
后,线程就进入可运行状态。- 在这个状态下,线程可能正在运行,这时线程是运行中(Running)
- 也可能正在等待系统为其分配处理器资源,这时线程是就绪(Ready)
- 阻塞(Blocked): 当线程试图获取一个锁(synchronized资源)而该锁被其他线程持有时,则该线程进入阻塞状态。线程会在这个状态下等待,直到它可以获取到锁。
- 等待(Waiting): 当线程等待其他线程完成特定操作时,它会进入等待状态。例如,当线程调用
Object.wait()
、Thread.join()
或LockSupport.park()
时,它会进入这个状态。 - 超时等待(Timed Waiting): 这个状态与等待状态类似,但有时间限制。例如,当线程调用
Thread.sleep(long millis)
、Object.wait(long timeout)
、Thread.join(long millis)
或LockSupport.parkNanos()/parkUntil()
时,它会进入定时等待状态。 - 终止(Terminated): 当线程的
run()
方法执行完成后,它会进入终止状态。这意味着线程的任务已经结束了。
状态转换
几个方法的比较
Thread.sleep(long millis)
:当前线程调用该方法后,线程进入超时等(TIMED_WAITING
)状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态(Ready
)。注意,该方法不会释放任何监视器(锁),如果一个线程持有锁,当它调用sleep()
方法时,它仍然会保持对这些锁的持有。这意味着其他需要这些锁的线程可能会被阻塞,直到该线程醒来并释放锁。Thread.yield()
:当前线程调用该方法后,线程会放弃获取的CPU时间片,但不释放锁资源,线程由运行状态(Running
)变为就绪状态(Ready
),让操作系统再次选择线程。它的作用是使当前正在执行的线程暂停执行,给其他同等优先级的线程执行的机会,但并不保证一定会轮流执行,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()
不会导致阻塞。该方法与sleep()
类似,只是不能由用户指定暂停多长时间Thread.join()/Thread.join(long millis)
:当前线程里调用其它线程T
的join()
,当前线程进入WAITING/TIMED_WAITING
状态,当前线程不会释放已经持有的对象锁。线程T
执行完毕或者millis
时间到,当前线程一般情况下进入RUNNABLE
状态,也有可能进入BLOCKED
状态(因为join是基于wait实现的)。Object.wait()
:当前线程调用对象的wait()
方法,当前线程释放对象锁,进入等待队列。依靠notify()
/notifyAll()
唤醒或者wait(long timeout)
timeout时间到自动唤醒。需要注意的是Object.wait()
必须要在同步代码块或同步方法中调用,这意味着调用它的线程必须持有该对象的监视器锁。当一个线程调用wait()
时,它会释放持有的监视器锁并进入等待状态,则其他线程就可以获取锁并执行同步的代码块或方法。Object.notify()
:当一个线程调用notify()
时,它会从这个对象的等待池中随机唤醒一个正在执行wait()
方法的线程。被唤醒的线程会尝试重新获得监视器锁。一旦获得锁,它就可以继续执行。notifyAll()
唤醒是在此对象监视器上等待的所有线程。与wait()
方法一样,notify()
也必须在同步代码块或同步方法中调用LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines)
:该类方法用于阻塞当前线程,直到获得许可(permit)或者线程被中断。调用这类方法的线程进入WAITING/TIMED_WAITING
状态。可以通过LockSupport.unpark(Thread thread)
方法被唤醒。LockSupport
使用一种许可(permit)机制。每个线程都有一个与之关联的许可,许可不能累积,它的数量只能是 0 或 1。- 如果一个线程已经拥有许可,那么
park()
方法将立即返回,然后消耗这个许可。如果没有许可,park()
会阻塞线程。 - 当调用
unpark()
时,会给指定线程发放一个许可(如果它之前没有许可的话),从而使得被park()
阻塞的线程返回。