一、线程的状态图
在这里插入图片描述理解线程的生命周期和线程的一个状态可能会有利于理解多线程的api和原理。
- 线程的状态:
-
简单的一个线程生命周期的图解:
Thread lifecycle - jdk代码中的线程的State枚举描述了线程的状态。
A thread can be in only one state at a given point in time.(jdk8)
java在Thread中使用一个枚举来介绍了线程的状态,结合上图来分析一下各个状态对应的代码
public enum State {
/**
* Thread state for a thread which has not yet started.
* 在线程未执行start方法时候为一个NEW状态。
*/
NEW,
问题思考:当new Thread时是否在jvm级别是否申请了线程,其实可以注意源码在Thread类的构造方法中
一直跟下去会发现有native方法,说明在new Thread()时有jvm层面上就有线程的产生。
/**
* 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.
* 这个状态表示,线程入上图进入了就绪队列,为可运行状态,也就是执行了start()方法,线程具有可执 行run方法代码的能力。
*/
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}.
* 目前的状态是线程处于阻塞状态,在前面文章分析java中的锁时,遇到一些锁的竞争时候,线程一直等待锁的一个释放,或者遇到同步的时候,线程的一个主动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 <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
* 这里的注释介绍了这个状态的一些情况,翻译一下就是线程处于这个状态是因为如下的几个方法:
* 1、调用wait方法,并且没有一个时间的限制;
* 2、线程的join方法,结合图一我们知道join方法,是等到线程执行完成。
* 3、锁调用的park方法。
*/
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;
}
二、how to kill the thread
- 今天记录的问题是如果结束或者说如何杀死运行中的线程,api中提供了stop,suspend方法,但是后续jdk遗弃了该方法,oracle也在其官网中给出了合理的解释,我这里提出网站的地址,有兴趣的同学可以了解一下。传送门,这里截取一段官方文档上的描述。
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked
(The monitors are unlocked as the ThreadDeath exception propagates up the stack.)
If any of the objects previously protected by these monitors were in an inconsistent state,
other threads may now view these objects in an inconsistent state.Such objects are said to be damaged.
When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and
difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads
silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself
at any time after the actual damage occurs, even hours or days in the future.
//这里解释,本质上是不安全的,因为强行的停止一个线程会造成他以往获得的锁全部被释放,如果有任何其
他对象被这些锁保护会造成一个不一致的状态,其他的线程现在可能对这些对象的视角也是一个不一致的状
态,这些对象就处于一种被损坏的状态,当有线程去操作这些被损坏的对象时,一些意想不到的情况会发生,
而且这些行为可能很微妙并且很难去探测,线程死亡的异常杀死线程悄无声息,因此,使用者不会得到任何的
警告,这写问题可能会发生在任何时间,甚至在几小时或者几天以后。
这段话第一次看会被绕晕的,简单的解释就是:目标线程可能会持有一个monitor,假如这个monitor控制着
某个逻辑,比如说A必须在B之前,现在来一个B在A之前,本来这个"错误的逻辑"会被这个monitor监听的,
但是就在这个时候,恰好收到一个stop指令,这个监视器被解锁,这就会导致意外的错误发生,但是可能也
不会发生,但是这种有风险的事谁会干呢,总结就是stop方法不安全,不建议使用。
- 下来官方文档介绍了为什么我们不可以捕获ThreadDeath异常,并且处理这个损坏的对象呢?
//文档主要介绍了两个原因:这里我贴出官方的说明:
1、A thread can throw a ThreadDeath exception almost anywhere. All synchronized methods and blocks
would have to be studied in great detail, with this in mind.
2、A thread can throw a second ThreadDeath exception while cleaning up from the first (in the catch or
finally clause). Cleanup would have to repeated till it succeeded. The code to ensure this would be quite
complex.
原因是一个线程会在任何地方抛出这个异常,所以我们需要在所得同步方法和代码块中解决这个问题,这个
是不和逻辑的。就算我们清理了第一次,也会在第二次的时候继续抛出,直到清理成功,这样代码的难度
太负责,所以是实现不了的。
- 那我们在平时的开发中如何去优雅的安全的停止一个线程了。文档中也给出了两个方法:
- 总结就是:1、条件变量;2、条件变量➕中断。
- 我们可以使用一个线程安全的变量或者线程的中断状态来优雅的关闭线程,下面介绍一下这两种情况。
1、使用线程安全的变量:
public class ThreadStop extends Thread{
private volatile boolean Stopflag=true;
public void stopRunning(){
Stopflag=false;
}
@Override
public void run() {
while (Stopflag){
System.out.println(Thread.currentThread().getName()+" is running");
}
System.out.println("Stoped Running");
}
}
class MainTest{
public static void main(String[] args) {
ThreadStop threadStop=new ThreadStop();
threadStop.start();
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
threadStop.stopRunning();
}
//这里我们使用一个线程安全的变量去用逻辑的方法去优雅的停止线程。
2、使用线程的中断状态停止线程。
public class ThreadInterrput extends Thread {
@Override
public void run() {
while (!Thread.interrupted()){
System.out.println(Thread.currentThread().getName()+"--is running");
}
System.out.println(Thread.currentThread().getName()+"-- is stopping");
}
}
class Test{
public static void main(String[] args) {
ThreadInterrput threadInterrput=new ThreadInterrput();
threadInterrput.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadInterrput.interrupt();
}
}
这里我们介绍一下java中鼎鼎大名的Interrupt
1、public void interrupt();//无返回值
2、public static boolean interrupted();//静态方法,有返回值
3、public boolean isInterrupted(); //有返回值
看到这里感觉很无语,一点都不符合命名规范嘛,这是要被批斗的,哈哈。
下面看一看这几个方法的源代码:
1、public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
中断本线程,并且没有返回值:这个方法会分为以下几种情况:
1、当该线程被wait,sleep,join方法阻塞时,该线程的中断状态会被清除,并收到一
InterruptedException
2、如果该线程正阻塞于interruptible channel上的I/O操作,则该通道将被关闭,同时该线程的中断状态被设
置,并收到一个java.nio.channels.ClosedByInterruptException。
3、如果该线程正阻塞于一个java.nio.channels.Selector操作,则该线程的中断状态被设置,它将立即从选择
操作返回,并可能带有一个非零值,就好像调用java.nio.channels.Selector.wakeup()方法一样。
4、如果上述情况都不符合,则线程的中断状态将会被设置。
第一种情况很特殊:阻塞于wait/join/sleep方法时中断状态会被清除同时收到异常;这里注意一点,如果是
其他线程中断当前线程,可执行checkAccess()方法,可能会抛出SecurityException
2、public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
检测当前线程是否已经中断,是返回true,不是返回false,并清除中断状态。
public boolean isInterrupted() {
return isInterrupted(false);
}
检测当前线程是否已经中断,是返回true,否返回false,中断状态不受影响,其实有上面方法的区别就是
前者会清除中断状态,而后者仅是读取状态。
三、suspend和resume函数
同样,suspend和resume函数也和stop函数一样被抛弃了,这个原因是什么呢,官方文档上也有一段解释,当线程调用suspend函数时,对于cpu是不可见的,cpu调度不到他,如果线程在suspend之前维护了一个monitor锁,在没有resume以前被,其他线程如果想要去获得这个monitor,就会造成一个死锁的状态。
四、总结:
对于如何停止一个线程,其实就是让它执行完毕,没有办法去立即停止一个线程,但是我们
可以控制何时或者什么条件下让他执行完毕。总结三点:
1、使用线程安全的变量来标示线程是否停止;
2、停止线程时,需要调用停止线程的interrupt()方法,因为可能线程在wait或者sleep,提高停止线程的及时性;
网友评论