网站首页 > 精选教程 正文
面试中经常问到的问题就是线程状态及状态之间的相互转换,虽然很基础,但是很重要,现在对相关知识进行总结。
线程中所有状态都在Thread.state枚举中定义,如下源码是获取线程状态的方法:
各种状态说明如下:
NEW:表示刚创建出来的线程,没有执行,也就是线程new完之后再执行start()方法之前的状态。
RUNNABLE:表示线程正在执行的状态,此状态的要素有两个:1.获得执行资源,比如获得锁 2.获得cpu执行权
BLOCK:阻塞状态,线程在执行过程中()遇到需要等待锁的情况是线程会处于BLOCK状态,阻塞状态获得锁后并且获得cpu执行权的时候会再次处于RUNNABLE状态
WAITING和TIMED_WAITING:这两个状体表示线程处于等待状态,区别在于WAITING处于一个无限期的等待,只能由外部事件唤醒,比如wait()方法等待的线程在等待notify()方法,通过join()方法等待的线程会等待目标线程终止。TIMED_WAITING是有时限的等待。
TERMINATED:线程执行完成之后进入TERMINATED,表示线程结束。
有些同学可能会对BLOCK状态和WAITING状态不太理解认为这都是处于“不能执行的状态”,有什么区别?我们可以从源码注释中得到答案:
BLOCK状态注释:
/**
* 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}.
*/
可以简单理解BLOCK状态就是在等待一个锁,线程获得锁之前处于的状态。
WAITING状态注释:
/**
* 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>
*/
可以简单理解调用注释中三个方法都会处于WAITING状态,就是这么简单。
- 新建线程
新建线程有两种方式,继承Thread类和实现Runnable接口,但最终都得调用start()方法,该方法会有两个操作:a.开启一个新的线程 b.让新的线程执行执行run方法。继承和实现的主要不同在于执行run方法的方式不同。
- 终止线程
线程Thread类提供了stop方法,但是该方法已经被标注为@Deprecated,也就是JDK并不推荐使用该方法,这是因为stop方法过于简单粗暴,它会把正在执行的线程强行终止,并立即释放线程持有的锁,我们知道锁在多线程中的意义就是保证对象(数据)一致性,当锁被强行释放的时候,很有可能导致数据不一致的问题。那如何安全的停止线程?
其实从stop()方法停止线程的方式我们可以知道,当一个线程需要停止的时候,外界过于暴力的终止都会强行释放锁,导致数据不安全,既然这样那只有让线程自己判断何时能够推出并保证数据安全,具体方法如下:
- 设置中断标志位:Thread.currentThread().interrupt();
- 线程通过中断标志位判断是否该退出(线程中断标志位可以简单理解为bool变量,运行时状态为false,设置中断标志位之后为true),这个逻辑是自己手动写,一般在线程逻辑处理开始的地方,代码如下:
if(Thread.currentThread().isInterrupted()){
break;
}
在这里还有特殊情况,在设置线程中断标志位之后,一旦线程调用了wait(),join(),sleep()方法之后,会立即抛出InterruptedException,并且将中断标志位清除,所以以上方法try catch捕捉到InterruptedException之后会做后续处理,比如直接退出。但是我们保证线程安全的原则就是让线程自己选择退出的时机,所以安全的方法是继续设置一个中断标志位,然后线程根据中断标志位自己退出,代码如下:
try{
Thread.sleep(2000);
}catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
线程中断总结:线程是否退出是根据中断标志位自己判断,如果检测中断标志位为true,那线程在处理完当前逻辑之后退出,所以线程中断的原理是:开发人员根据中断标志位的具体值来决定是否退出线程。
- 等待和有时限等待
和等待状态相关的两个方法就是wait()和notify(),如果一个线程调用了object.wait(),那这个线程就会进入wait的等待队列,并且释放锁;释放之后其它线程会获取该锁,等其它执行的线程调用object.notify()。就会从object的等待队列中随机选取一个线程唤醒。被唤醒的线程不会立即执行,它要等待object锁被释放,获取锁之后才会真正地去执行。所以wait()和notify()这两个方法都要在获取锁之后执行。通俗的理解就是:这两个方法操作的是对象上线程的等待队列,所以必须要获取该对象才能对其队列进行操作。
有时限的等待sleep和wait都能实现,他们之间主要有两个差别:a.wait()会等待外部唤醒 b.wait会释放锁,sleep不会释放锁,所以sleep睡眠时间到之后会直接执行。
- 线程挂起(suspend)和继续执行(resume)
这两个方法在jdk中已经标注不推荐使用,两个原因:1.在线程挂起的过程当中,不会释放任何资源 2.suspend和resume总是成对出现,且理论上来说前者在后者之前执行,但是万一先执行resume后执行suspend,那被挂起的线程很难在执行。所以可以看出如果在程序中使用这两个方法,系统的锁资源会极度依赖业务代码,这是非常危险的。
- 等待线程结束(join)和谦让(yield)
这两个操作是线程合作的操作,join的操作会将调用者线程在当前执行线程之前执行,本质是让当前线程等待在调用线程实例对象上。当调用线程执行完成之后再调用notifyAll()通知所有等待线程继续执行。所以我们在使用的时候最好不要在对象实例上调用wait()和notify()方法,这很有可能影响系统api。
yield()方法调用之后,调用者会让出cpu,然后重新和其它等待线程一起再抢夺cpu的执行权。
线程实现:
线程实现主要有三种方式:使用内核线程,使用用户线程,使用用户线程加轻量级线程混合。
内核线程:内核线程是直接由操作系统内核操作的线程,每个内核线程都可以视为一个虚拟内核,帮助操作系统同时处理多件事情。内核线程只会运行在内核态,不会因为内核态和用户态的线程切换而损失性能。内核线程的运行肯定会消耗内核资源如内核栈和寄存器。所以内核线程是极其宝贵的资源,一个系统支持内核线程的数量是有限的。
轻量级线程:内存线程还有一种特殊线程-轻量级线程,这也是jvm虚拟机线程的实现方式,它是内核线程的高度抽象,每个轻量级线程都会由一个内核线程去对应支持做和系统相关的工作,这也是轻量级线程的局限所在:它所有和操作系统相关的操作如线程的创建,等待,挂起和线程状态相关的操作都是native的方法(如下图),所以在操作这些方法的时候会频繁地需要在用户态和内核态切换,非常消耗性能,具体的说当java代码中的方法在调用native方法的时候,此时cpu要从执行用户线程切换到执行内核线程。在切换的时候要先把用户线程的执行现场如寄存器,程序计数器,内存中的相关数据保存起来,之后执行内核线程,内核线程执行完成之后再恢复用户线程的执行线程。这种保存和恢复执行现场的操作包含数据的复制,所以非常消耗性能。这块的介绍关联jvm内存的本地方法区,本地方法区通过本地方法接口操作本地方法库,所以JVM通过本地方法接口实现和底层操作系统的交互,java中native方法的 执行是在JVM内存模型中的本地方法栈中,本地方法栈一般是调用系统底层的方法,这又涉及到用户态线程切换到内核态线程,可以理解成本地方法栈中执行的方法大概率会切换到内核态线程。
用户线程:用户线程值得是完全建立在用户空间的线程库,用户线程的建立,同步,销毁,调度都在用户态中完成,系统内核不能感知到用户线程的存在。所以用户线程的优势是不需要内核线程支持,但是这也是它的劣势-所有的线程状态变更都要用户线程自己处理,操作起来异常繁琐。
混合实现:混合实现,是用户线程和内核线程共存的一种实现方式。 用户线程还是完全建立在用户空间中,而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,这大大降低了整个进程被完全阻塞的风险。
- 上一篇: 洛阳课工场—Java学习中线程的6种状态有哪些
- 下一篇: Java线程池核心(十一):线程池状态
猜你喜欢
- 2024-11-18 Java并发编程线程状态转换
- 2024-11-18 Java面试必考问题:线程的生命周期
- 2024-11-18 Java 19 虚拟线程的状态变化,停驻与锁定
- 2024-11-18 Java线程生命周期详解?
- 2024-11-18 线程从创建最终消亡,要经历的若干状态,你了解其中的多少?
- 2024-11-18 java线程状态之WAITING(等待)
- 2024-11-18 浅谈Java线程:线程基础知识扫盲
- 2024-11-18 Java线程在各个状态下调用start方法会发生什么事情?
- 2024-11-18 为什么Java中线程没有Running状态
- 2024-11-18 阻塞模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?
你 发表评论:
欢迎- 04-11Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- 04-11Java中你知道几种从字符串中找指定的字符的数量
- 04-11探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- 04-11Python字符串详解与示例(python字符串的常见操作)
- 04-11java正则-取出指定字符串之间的内容
- 04-11String s1 = new String("abc");这句话创建了几个字符串对象?
- 04-11java判断字符串中是否包含某个字符
- 04-11关于java开发中正确的发牌逻辑编写规范
- 最近发表
-
- Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- Java中你知道几种从字符串中找指定的字符的数量
- 探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- Python字符串详解与示例(python字符串的常见操作)
- java正则-取出指定字符串之间的内容
- String s1 = new String("abc");这句话创建了几个字符串对象?
- java判断字符串中是否包含某个字符
- 关于java开发中正确的发牌逻辑编写规范
- windows、linux如何后台运行jar(并且显示进程名)
- 腾讯大佬私人收藏,GitHub上最受欢迎的100个JAVA库,值得学习
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)