Java线程生命周期的终止与复位
作者:??益发不可收拾???? 发布时间:2022-04-22 01:57:01
Thread生命周期
生命周期概述
Java的线程状态描述放在Thread
类里面的枚举类State
中.总共包含了6中状态(从出生到死亡)。
public enum State {
/**
* 尚未启动的线程的线程状态 (没有start)
*/
NEW,
/**
* 可运行线程的线程状态,是可以运行的线程状态(并不是在运行)
* 这个状态在Java虚拟机中进行,但它可能等待来自操作系统的其他资源,比如CPU。
* 内部包含了两个状态 【RUNNING】,【READY】这两个状态是可以互相流转的
* 调用了start后线程就处于 READY 状态 ,等待操作系统分配CPU时间片,分配后进入 RUNNING 状态。
* 当调用 yield() 方法后,只是谦让的允许当前线程让出 CPU ,但是不一定让,由操作系统决定,如果让 * 了当前线程就会进入 READY 状态,等待系统分配CPU时间片再次进入 RUNNING 状态。
*/
RUNNABLE,
/**
* 阻塞状态。
* 线程阻塞,等待监视器锁的状态,获取监视器锁后会进入 RUNNABLE 状态
* 当发生线程锁竞争状态下,没有获取到锁的线程会被挂起进入阻塞状态,比如synchronized锁。
*/
BLOCKED,
/**
* 等待线程的线程状态
* 线程调用以下方法会处于等待状态:Object.wait()不超时、Thread.join()不超时等方法
* 一个处于等待状态的线程正在等待另一个线程执行特定动作,例如:
* 一个线程调用了Object.wait()方法在一个对象上正在等待另一个线程调用Object.nofify()或者
* Object.nofifyAll()方法开启那个对象
* 一个调用了Thread.join()方法的线程正在等待指定线程终止
*/
WAITING,
/**
* 具有指定等待时间的等待线程的线程状态,调用一下方法会处于这个状态: Object.wait() 超时、 * Thread.join()超时 Thread.sleep(long) 等方法
*/
TIMED_WAITING,
/**
* 已终止线程的线程状态
* 线程执行完毕或者发生异常终止执行
*/
TERMINATED;
}
线程生命周期流程图
线程生命周期测试
public class ThreadStatusDemo {
public static void main(String[] args) throws InterruptedException {
// 测试 NEW RUNNABLE TERMINATED
Thread terminated_thread = new Thread(() -> {
long start = System.currentTimeMillis();
// 运行三秒 ,打印TERMINATED_THREAD线程runnable状态
while (System.currentTimeMillis()-start<3000){}
}, "TERMINATED_THREAD");
// NEW
Thread.State state = terminated_thread.getState();
System.out.println(terminated_thread.getName()+" :state = " + state);
terminated_thread.start();
TimeUnit.SECONDS.sleep(1);
// RUNNABLE
Thread.State state1 = terminated_thread.getState();
System.out.println(terminated_thread.getName()+"state1 = " + state1);
TimeUnit.SECONDS.sleep(5);
Thread.State state2 = terminated_thread.getState();
// TERMINATED
System.out.println(terminated_thread.getName()+"state2 = " + state2);
// RUNNABLE
new Thread(() -> {
while (true) {
}
}, "Runnle_Thread").start();
// TIMED_WAITING
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Time_Waiting_Thread").start();
// WAITING
new Thread(() -> {
while (true) {
synchronized (ThreadStatusDemo.class) {
try {
ThreadStatusDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Waiting_Thread").start();
// 这两个看谁先抢占到cpu获得锁,另一个就blocked
// timed_waiting
new Thread(new BlockedDemo(), "Blocke01_Thread").start();
// blocked
new Thread(new BlockedDemo(), "Blocke02_Thread").start();
}
static class BlockedDemo extends Thread {
@Override
public void run() {
synchronized (BlockedDemo.class) {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
启动线程
java中的启动
Java
启动一个线程调用start
方法,start方法内部调用了 start0()
native方法。
public synchronized void start() {
. . .
boolean started = false;
try {
// 调用native方法
start0();
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 */
}
}
}
这个测试是为了验证上图的正确性,只贴了部分.
Hotspot中的启动
查看指引:
在jvm.cpp
找到JVM_StartThread
方法。发现是先创建个 JavaThread
作为本地线程然后启动这个本地线程(借助os【thread.cpp】,因为jvm是跨平台的,这里是以linux-os为示例)
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
{
MutexLocker mu(Threads_lock);
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
// 先创建一个JavaThread
native_thread = new JavaThread(&thread_entry, sz);
if (native_thread->osthread() != NULL) {
native_thread->prepare(jthread);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
delete native_thread;
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
"unable to create new native thread");
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}
// 然后启动这个本地线程 thread.cpp
Thread::start(native_thread);
JVM_END
JavaThread 创建线程:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
#if INCLUDE_ALL_GCS
, _satb_mark_queue(&_satb_mark_queue_set),
_dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
if (TraceThreadEvents) {
tty->print_cr("creating thread %p", this);
}
initialize();
_jni_attach_state = _not_attaching_via_jni;
set_entry_point(entry_point);
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread
// 调用os(操作系统)创建个线程
os::create_thread(this, thr_type, stack_sz);
_safepoint_visible = false;
. . .
}
thread.cpp 启动线程:
// tips: 启动线程的方法
void Thread::start(Thread* thread) {
trace("start", thread);
// Start is different from resume in that its safety is guaranteed by context or
// being called from a Java method synchronized on the Thread object.
if (!DisableStartThread) {
if (thread->is_Java_thread()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
// tips:启动之后设置线程的状态为 可运行状态 RUNNABLE
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
// 借助操作系统启动线程
os::start_thread(thread);
}
}
线程中断与复位
不要使用stop方法
线程的终止不要简单的调用 stop
方法,这个方法和其他的线程控制方法(suspend
,resume
)一样都是过期了不建议使用的,这些方法都是不安全的。 例如stop()
方法在结束一个线程的时候并不保证线程资源的正常释放,因此可能导致出现一些不确定的状态。 按照人类逻辑来理解:T1线程调用方法修改T2线程的状态,但是T2现在在做什么T1是不清楚的,所以强制他关闭就是不安全的,就好比在Linux
中使用 kill -9
杀掉一个进程。
使用interrupt方法
interrupt()
方法只是修改了被中断线程的中断标志 ,并没有做什么过分的事儿。就像平时写代码的时候修改某对象的标志,对象自己通过标志类决定执行什么逻辑。这里也是一样,interrupt()
方法修改中断标志,被中断的线程,自己决定做什么事儿(中断或者不中断都是被中断线程自己决定的,外部只是通知他,不是强迫他)。追一下源码。
1.Java调用interrupt
方法
2.通过指引找到 jvm.cpp#JVM_Interrupt
方法
thread.cpp interrupt 借用操作系统。直接通过系统调用 interrupt
void Thread::interrupt(Thread* thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
// tips: 调用操作系统的interrupt方法
os::interrupt(thread);
}
这里还是以os_linux.cpp为例最终调用osthread的set_interrupted修改状态
这里就印证了上方的 Thread.interrupt()
只是修改了线程的一个标志位 ,并没有做什么过分的事儿。
线程的复位
interrupted
与isInterrupted
这两个放在一起是因为他们底层都是调用的同一个native方法isInterrupted()
只是给了不同的入参。 再就是,有过面试官问到他两的区别,所以干脆放在一起。首先说结论 ,isInterrupted()
会返回线程的中断状态,interrupted()
不仅会返回中断状态,而且如果线程处于状态状态还会将线程终端状态复位(清除中断状态)。
os_linux.cpp的is_interrupted()
方法印证了上面说的isInterrupted()
会返回线程的中断状态,interrupted()
不仅会返回中断状态,而且如果线程处于状态状态还会将线程终端状态复位(清除中断状态)。
其他的线程复位
在Java
中只要抛出了InnterruptException
异常的方法都对线程进行了复位。先理顺下为什么要这么做:查看下基本上抛出InnterruptException
异常的方法都是线程阻塞方法,比如sleep()
,wait()
,join()
。这类方法执行后线程会处于TIMED_WAITING
或者WAITING
状态,处于这类状态的线程是不受控的(线程丧失了对自己的主导,需要其他的线程唤醒,或者阻塞时间到达才能拥有自己的主导权),这个时候线程中断,线程自己却没办法处理。甚至可能永远等不到释放而无法执行中断。所以,在线程是中断状态下,执行方法让线程阻塞,就要抛出一个异常告诉外界 ,我现在是阻塞状态,并且将中断标记复位,方便外界进行处理(例如中断线程的执行或者继续阻塞方法),相当于给了外界一个改变线程状态的入口。 以sleep()
为例追踪下源码:
通过指引找到 jcm.cpp#JVM_Sleep
方法入口就直接判断线程的中断状态了 ,is_interrupted()
上面介绍过了,参数为true
就是清除中断标志并且返回清除之前的中断状态。这里线程是中断状态的就直接抛出 InnterruptException sleep interrupted
异常了。
来源:https://juejin.cn/post/7115685645749583885
猜你喜欢
- Java中有四种权限修饰符publicprotected(default)private同一个类yesyesyesyes同一个包yesyes
- 一、# List泛型集合集合是OOP中的一个重要概念,C#中对集合的全面支持更是该语言的精华之一。为什么要用泛型集合?在C# 2.0之前,主
- 在做项目时,需要一个定时任务来接收数据存入数据库,后端再写一个接口来提供该该数据的最新的那一条。数据保持最新:设计字段sign的值(0,1)
- Elastic Search是一个开源的,分布式,实时搜索和分析引擎。Spring Boot为Elasticsearch及Spring Da
- 本文实例总结了C#中string.format用法。分享给大家供大家参考。具体分析如下:String.Format 方法的几种定义:Stri
- 在一些需要经常更新页面数据的网站中,一般访问量不是很大的都直接发布的是带后台代码,每次访问都是有数据库交互的。但是一旦访问量增加了,那么这些
- 本文介绍IntelliJ IDEA中Project 窗口的一些设置技巧,参考IntelliJ IDEA 简体中文专题教程,英文好的同学可以查
- 这篇文章主要介绍了Java数组索引异常产生及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- 这篇文章主要介绍了springboot项目访问静态资源的配置代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习
- 也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前,我们今天先来讨论一些比较
- public static string Replace(string source, string match, string repla
- 一、数据类型与变量的介绍在程序运行的过程中计算机需要记录大量的状态 数据(这里我们统称数据)。那这些数据都存放在哪呢?程序在运行过程中的数据
- 文章来源:aspcn 作者:孙雯服务器Sockets列表9.2是一个服务器应用程序的一部分.列表9.2 一个简单的服务器程序 /** &n
- java实现在线预览- -之poi实现word、excel、ppt转html,具体内容如下所示:###简介java实现在线预览功能是一个大家
- 1.问题由来迷宫实验是取自心理学的一个古典实验。在该实验中,把一只老鼠从一个无顶大盒子的门放入,在盒中设置了许多墙,对行进方向形成了多处阻挡
- 简介MapStruct 是一个代码生成器(可以生成对象映射转换的代码),它基于约定优于配置的方法,极大地简化了 Java bean 类型之间
- 文件上传在web应用中非常普遍,要在jsp环境中实现文件上传功能是非常容易的,因为网上有许多用java开发的文件上传组件,本文以common
- 1.Spring bean组件 ”默认为单例模式scope=“singleton, 运行JavaApplication容器启动时自动创建对象
- 早期的项目比较简单,多是用JSP 、Servlet + JDBC 直接搞定,后来使用 Struts1(Struts2)+Spring+Hib
- Java中普通代码块,构造代码块,静态代码块区别及代码示例//执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块