经过前面的系列文章,我们已经学习了线程的创建、并发协同常用工具类、锁等知识了,现在我们回过头来学习线程的运行状态,有了前面的知识,理解线程的运行状态就更容易了。下面我们详细学习下线程的运行状态,然后通过手写简易线程池实战例子来演示线程运行状态的变化过程。
线程运行状态枚举
线程的运行状态在Thread类中用一个枚举State表示。
/**
* A thread state. A thread can be in one of the following states:
* <ul>
* <li>{@link #NEW}<br>
* A thread that has not yet started is in this state.
* </li>
* <li>{@link #RUNNABLE}<br>
* A thread executing in the Java virtual machine is in this state.
* </li>
* <li>{@link #BLOCKED}<br>
* A thread that is blocked waiting for a monitor lock
* is in this state.
* </li>
* <li>{@link #WAITING}<br>
* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* </li>
* <li>{@link #TIMED_WAITING}<br>
* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* </li>
* <li>{@link #TERMINATED}<br>
* A thread that has exited is in this state.
* </li>
* </ul>
*
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
/**
* Returns the state of this thread.
* This method is designed for use in monitoring of the system state,
* not for synchronization control.
*
* @return this thread's state.
* @since 1.5
*/
public State getState() {
return threadState();
}
/**
* Returns the state of this thread.
* This method can be used instead of getState as getState is not final and
* so can be overridden to run arbitrary code.
*/
State threadState() {
return jdk.internal.misc.VM.toThreadState(holder.threadStatus);
}
一个线程可以处于以下状态之一:
NEW:尚未启动的线程处于此状态。
RUNNABLE:在Java虚拟机中执行的线程处于此状态。
BLOCKED:被阻塞等待监视器锁的线程处于此状态。
WAITING:无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING:等待另一个线程执行操作,但只等待指定时间的线程处于此状态。
TERMINATED:已退出的线程处于此状态。
在任意给定时间点,一个线程只能处于一种状态。这些状态是虚拟机状态,并不反映任何操作系统线程状态。因为
Java线程运行状态是JVM层面的抽象概念,而操作系统线程状态是内核层面的实现细节。操作系统线程通常只有3
种基本状态:就绪(Ready)、运行(Running)和等待(Waiting),而Java线程运行状态则更细化为6种。
6种状态需要注意的是BLOCKED和WAITING、TIMED_WAITING,三者都有等待的意思,但是等待的对象不同,
BLOCKED等待的是监视器锁,WAITING、TIMED_WAITING等待的是另一个线程完成特定的操作。
线程运行状态详解
NEW
新建状态。通过new 关键字创建了Thread对象还没有调用start()方法时,线程处于NEW状态,比如Thread t = new Thread()。
RUNNABLE
可运行状态。线程对象调用start()方法后处于可运行状态,等待系统调度,处于可运行状态的线程正在Java虚拟机中执行,但可能正在等待操作系统的其他资源(如处理器)。
BLOCKED
阻塞状态。线程因等待监视器锁而阻塞的状态。处于阻塞状态的线程正在等待进入同步代码块/方法,或在调用Object.wait()后重新进入同步代码块/方法
WAITING
等待状态。线程进入等待状态是由于调用了以下方法之一:
(1)无超时的Object.wait()
(2)无超时的Thread.join()
(3)LockSupport.park()
处于等待状态的线程正在等待另一个线程执行特定操作。例如:调用Object.wait()的线程等待其他线程调用该对象
的notify()/notifyAll();调用Thread.join()的线程等待指定线程终止。前一篇我们已经学习了LockSupport的相关知
识,LockSupport.park()迫使当前线程进入等待状态,等待另一个线程以当前线程为目标唤醒当前线程。
TIMED_WAITING
具有指定等待时间的等待状态。线程进入限时等待状态是由于调用了以下带正数超时参数的方法:
(1)带超时的Thread.sleep(long)
(2)带超时的Object.wait(long)
(3)带超时的Thread.join(long)
(4)带超时的parkNanos(Object blocker, long nanos)
(5)带超时的LockSupport.parkUntil(Object blocker, long deadline)
TERMINATED
终止状态。线程已完成执行。在系列文章的第一篇,我们已经学习了线程正确停止的方式,run()方法执行完成后
线程自然进入终止状态、被废弃的终止线程的方式stop()和suspend()方法、调用interrupt()方法中断处于阻塞状
的线程进入终止状态。还有其他方式,一种方式是通过volatile变量或原子布尔型变量实现优雅的终止线程。
public class WorkerThread extends Thread {
//volatile变量
private volatile boolean running = true;
@Override
public void run() {
while(running) {
// 执行任务
}
// 正常终止
}
public void stopRunning() {
running = false; // 触发终止
}
}
另一是异常终止方式。比如:
Thread thread = new Thread(() -> {
throw new RuntimeException("未处理的异常");
});
thread.start();
总结下线程进入终止状态的方式:
(1)run()方法自然终止。
(2)通过volatile变量或原子布尔型变量实现优雅的终止线程。
(3)调用interrupt()方法中断处于阻塞状的线程。
(4)异常终止。
(5)调用stop()和suspend()方法(废弃,不推荐)。
手写简易线程池需求分析
下面通过分析、手写一个简易线程池,理解线程的运行状态,手写的目的不是重复造轮子开发一个完善的线程池,而是通过这个过程加深对线程各个运行状态之间的相互转换过程。先不看jdk里面已经提供好的线程池的具体实现,我们从常规的思路去分析。
线程池基本要素分析
首先我们来分析下一个线程池有哪些基本要素。一个线程池里面有两个主要对象,一个是任务队列,一个是线程。任务队列用来接收外部提交的任务,线程从任务队列里面取出任务执行。
既然是一个池,那么它应该有个边界或者说容量,就像一个池塘里面,能样多少鱼。所以线程池里面应该可以控制线程的数量,可以控制队列的长度。
接着多个线程处理任务的时候,要保证有序的处理任务,这就涉及线程之间的并发协同控制了。那么用什么工具呢?前面我们已经学过了相关知识,有synchronized+wait/notify,CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock等多种工具可以选择。
线程池关键功能分析
分析了线程池的基本要素,接着来分析线程池关键功能。首先,程序启动的时候,线程池需要一个初始化的功能,预创建核心线程并启动,初始化任务队列长度,此时的线程从新建状态转换为可运行状态。
接着线程池可以接收外部提交的任务了,所以应该有一个提交任务的方法,然后线程从队列里面获取任务并执行。队列为空时,线程进入等待状态,有任务则处于运行状态。
最后,线程池应该有关闭功能,任务执行完成了,可以自动关闭线程池的运行,或者我们想关闭的时候就关闭。所以线程池应该有个关闭方法,关闭线程池实际上是停止线程池里面线程的运行,停止线程的正确方式,我们也学习了。停止线程后线程进入终止状态。
核心要素总结:
(1)任务队列:采用线程安全的阻塞队列存储待执行任务,需实现任务提交/获取的同步控制。
(2)工作线程:维护固定数量或可扩展的线程组,每个线程循环从队列获取任务执行。
(3)数量控制:线程数量、队列长度参数配置。
(4)同步控制:需使用并发协同工具保证队列线程安全,实现线程阻塞唤醒。
关键功能总结:
(1)线程池初始化:,预创建核心线程并启动,设置任务队列容量限制。
(2)任务提交:提供提交任务方法添加Runnable任务。
(3)线程调度:工作线程循环获取并执行任务。
(4)终止控制:优雅关闭(等待任务完成),立即终止(中断所有线程)。
手写简易线程池代码实现
上述手写简易线程池需求分析的整个过程基本涵盖了线程从新建状态到终止状态的整个生命周期,下面我们用代码来实现这个过程。
package com.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
public class MyThreadPool {
//任务队列
private static BlockingQueue<Runnable> taskQueue;
//工作线程集合
private static List<XmlWorker> workerList;
//线程池工作状态标志,用来控制线程池的运行状态
private static volatile boolean working = true;
//线程池初始化
public MyThreadPool(int poolSize, int queueSize) {
//初始化线程池时需要创建指定长度的任务队列、创建指定数量的核心线程
this.taskQueue = new ArrayBlockingQueue<>(queueSize);
this.workerList = new ArrayList<>(0);
for (int i = 0; i < poolSize; i++) {
//创建线程对象时传入线程池对象
XmlWorker worker = new XmlWorker(this);
System.out.println(worker.getName() + " create,status:" + worker.getState());
worker.start();
System.out.println(worker.getName() + " start,status:" + worker.getState());
workerList.add(worker);
}
}
/**
* 线程对象
*/
private class XmlWorker extends Thread {
//线程要从线程池队列里取任务,所以要注入线程池对象到、取任务要写在run方法里
MyThreadPool pool;
public XmlWorker(MyThreadPool pool) {
this.pool = pool;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run,status:" + Thread.currentThread().getState());
//线程池处于工作状态时,监听任务
while (working) {
//任务放进来时是Runnabled对象,取出来时也是Runnable对象,所用用Runnable接收
Runnable task = null;
if (pool.taskQueue.size() > 0) {
try {
//阻塞获取,线程处于等待状态直到有任务
task = pool.taskQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//pool.submitTask()放任务的时候并没有执行,task.run() 才真正执行
if (null != task) {
task.run();
}
}
}
}
/**
* 用Runnable对象来表示任务
*
* @param task
*/
private static void submitTask(Runnable task) {
try {
//线程池处于工作状态时才能提交任务
if (working) {
taskQueue.put(task);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
*
*/
private void shutdown() {
working = false;
//循环遍历线程,有线程阻塞时,要关闭线程,有线程在运行时不能关闭线程
for (XmlWorker t : workerList) {
Thread.State state = t.getState();
if (state == Thread.State.BLOCKED || state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
//使用t.interrupt()正确停止线程,否则程序仍然处于运行状态
System.out.println(t.getName() + " waiting or blocked,status:" + t.getState());
t.interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
//程序启动时,创建线程池
MyThreadPool pool = new MyThreadPool(3, 3);
CountDownLatch countDownLatch = new CountDownLatch(3);
//主线程往线程池提交任务
for (int i = 0; i < 3; i++) {
//间隔200毫秒提交一个任务
Thread.sleep(2000);
System.out.println("Thread " + Thread.currentThread().getName() + " submitTask" + i);
pool.submitTask(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " print task");
}
});
countDownLatch.countDown();
}
//主线程等待任务提交完成
countDownLatch.await();
for (XmlWorker t : workerList) {
System.out.println("Task submit finish " + t.getName() + " status:" + t.getState());
}
//关闭线程池
pool.shutdown();
Thread.sleep(1000);
for (XmlWorker t : workerList) {
System.out.println("Pool shutdown " + t.getName() + " status:" + t.getState());
}
}
}
上面的代码,创建线程池时初始化线程数量和队列的数量都是3,ArrayBlockingQueue内部已经帮我们用ReentrantLock实现了多线程任务提交和获取的同步控制,所有我们直接拿来用即可。
//提交任务
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while(this.count == this.items.length) {
this.notFull.await();
}
this.enqueue(e);
} finally {
lock.unlock();
}
}
//取任务
public E take() throws InterruptedException {
ReentrantLock lock = this.lock;
lock.lockInterruptibly();
Object var2;
try {
while(this.count == 0) {
this.notEmpty.await();
}
var2 = this.dequeue();
} finally {
lock.unlock();
}
return var2;
}
主线程间隔200毫秒提交一个任务,任务处理完后,线程处于等待状态,然后通过interrupt()中断线程使线程进入终止状态。下面看下线程处理任务过程中线程状态的变化过程:
Thread-0 create,status:NEW
Thread-0 start,status:RUNNABLE
Thread-1 create,status:NEW
Thread-1 start,status:RUNNABLE
Thread-2 create,status:NEW
Thread-2 start,status:RUNNABLE
Thread-0 run,status:RUNNABLE
Thread-1 run,status:RUNNABLE
Thread-2 run,status:RUNNABLE
Thread main submitTask0
Thread-1 print task
Thread main submitTask1
Thread-2 print task
Thread main submitTask2
Thread-0 print task
Task submit finish Thread-0 status:RUNNABLE
Task submit finish Thread-1 status:WAITING
Task submit finish Thread-2 status:WAITING
Thread-1 waiting or blocked,status:WAITING
Thread-2 waiting or blocked,status:WAITING
java.lang.InterruptedException
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1727)
at java.base/java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:420)
at com.demo.MyThreadPool$XmlWorker.run(MyThreadPool.java:54)
java.lang.InterruptedException
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1727)
at java.base/java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:420)
at com.demo.MyThreadPool$XmlWorker.run(MyThreadPool.java:54)
Pool shutdown Thread-0 status:TERMINATED
Pool shutdown Thread-1 status:TERMINATED
Pool shutdown Thread-2 status:TERMINATED
从输出结果可以看出,线程状态的变化过程:NEW->RUNNABLE->WAITING->TERMINATED
总结
本文开头先是讲解了线程的运行状态枚举,最后通过手写简易线程池的方式,分析线程池内部线程的运行过程,通过这个过程演示线程运行状态的变化过程,希望能让大家深刻理解线程状态的转换过程而不是死记硬背。
937

被折叠的 条评论
为什么被折叠?



