如何终止一个线程
是使用 thread.stop() 吗?
public class ThreadDemo extends Thread{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("this is demo thread :"+Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ThreadDemo t = new ThreadDemo();
t.start();
System.out.println("this is main thread :"+Thread.currentThread().getName());
t.stop();
}
}
实际代码中的stop方法是不建议使用的:
t.stop ();
Java 的rt.jar 包中的Thread类的 stop() 源码:
/**
* Forces the thread to stop executing.
* <p>
* If there is a security manager installed, its <code>checkAccess</code>
* method is called with <code>this</code>
* as its argument. This may result in a
* <code>SecurityException</code> being raised (in the current thread).
* <p>
* If this thread is different from the current thread (that is, the current
* thread is trying to stop a thread other than itself), the
* security manager's <code>checkPermission</code> method (with a
* <code>RuntimePermission("stopThread")</code> argument) is called in
* addition.
* Again, this may result in throwing a
* <code>SecurityException</code> (in the current thread).
* <p>
* The thread represented by this thread is forced to stop whatever
* it is doing abnormally and to throw a newly created
* <code>ThreadDeath</code> object as an exception.
* <p>
* It is permitted to stop a thread that has not yet been started.
* If the thread is eventually started, it immediately terminates.
* <p>
* An application should not normally try to catch
* <code>ThreadDeath</code> unless it must do some extraordinary
* cleanup operation (note that the throwing of
* <code>ThreadDeath</code> causes <code>finally</code> clauses of
* <code>try</code> statements to be executed before the thread
* officially dies). If a <code>catch</code> clause catches a
* <code>ThreadDeath</code> object, it is important to rethrow the
* object so that the thread actually dies.
* <p>
* The top-level error handler that reacts to otherwise uncaught
* exceptions does not print out a message or otherwise notify the
* application if the uncaught exception is an instance of
* <code>ThreadDeath</code>.
*
* @exception SecurityException if the current thread cannot
* modify this thread.
* @see #interrupt()
* @see #checkAccess()
* @see #run()
* @see #start()
* @see ThreadDeath
* @see ThreadGroup#uncaughtException(Thread,Throwable)
* @see SecurityManager#checkAccess(Thread)
* @see SecurityManager#checkPermission
* @deprecated This method is inherently unsafe. Stopping a thread with
* Thread.stop causes it to unlock all of the monitors that it
* has locked (as a natural consequence of the unchecked
* <code>ThreadDeath</code> exception propagating up the stack). If
* any of the objects previously protected by these monitors were in
* an inconsistent state, the damaged objects become visible to
* other threads, potentially resulting in arbitrary behavior. Many
* uses of <code>stop</code> should be replaced by code that simply
* modifies some variable to indicate that the target thread should
* stop running. The target thread should check this variable
* regularly, and return from its run method in an orderly fashion
* if the variable indicates that it is to stop running. If the
* target thread waits for long periods (on a condition variable,
* for example), the <code>interrupt</code> method should be used to
* interrupt the wait.
* For more information, see
* <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
*/
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(new ThreadDeath());
}
源码中标注了已弃用,并且给出来详细的解释和说明:
已弃用
这种方法本质上是不安全的。使用thread.stop停止线程会导致它解锁它锁定的所有监视器(这是未选中的ThreadDeath异常向堆栈上传播的自然结果)。
如果以前受这些监视器保护的任何对象处于不一致状态,则其他线程会看到损坏的对象,从而可能导致任意行为。stop的许多用法应该被简单地修改一些变量以指示目标线程应该停止运行的代码所取代。
目标线程应该定期检查该变量,如果该变量指示要停止运行,则以有序的方式从其运行方法返回。
如果目标线程长时间等待(例如,在条件变量上),则应使用中断方法中断等待。
操作系统在执行stop时,并不知道Java当前的线程情况。
假设run方法中有多条指令在执行。
如果通过stop强制终止,那么可能会导致数据不完整,所以不建议这样使用。
那如何解决呢?
线程中断
可以通过interrupt 来做。但是interrupt不是用来终止的方法,而是发送一个信号,需要对其进行额外处理才可以起到终止的效果。
public class InterruptDemo implements Runnable{
private int i=0;
@Override
public void run() {
//isInterrupted 默认false
while(!Thread.currentThread().isInterrupted()){
System.out.println("this is Thread : "+ (i++) +" - " +Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new InterruptDemo());
t.start();
Thread.sleep(1000);//主线程timed_waiting 1秒
t.interrupt();//设置 interrupted=true
}
}
执行结果:
......
this is Thread : 69 - Thread-0
this is Thread : 70 - Thread-0
this is Thread : 71 - Thread-0
Process finished with exit code 0
当我们的run
方法中,如果存在一个无法终止的操作时(比如while(true)
、比如sleep(200000)
)
它们会使线程阻塞,无法运行,也无法终止,这时要考虑使用interrupt 来中断。
线程复位
在Java线程中,针对阻塞的操作,如果想去唤醒它,它会提供一个中断的异常抛出。
但凡是让线程阻塞的机制(方法),都会抛出中断异常(InterruptedException
),需要去处理。
import java.util.concurrent.TimeUnit;
public class InterruptExceptionDemo implements Runnable{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
try {
TimeUnit.SECONDS.sleep(200);
} catch (InterruptedException e) {//触发了线程复位 isInterrupted-> false
e.printStackTrace();
}
}
System.out.println("thread is end.");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptExceptionDemo());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
控台打印结果:
我们中thread.interrupt() 后,run方法响应来异常,进入catch代码块。
由于触发了线程复位,所以isInterrupted 又重置为 false。
我们需要中catch块中对中断做出处理,可以有的处理方式:
- 如果不做处理,那么由于中断复位会使线程继续处于sleep状态。
- 继续中断,将选择权给到run方法,Thread.currentThread().interrupt(); //再次中断
- 抛出异常。
复位是JVM层面做的,它会在抛异常之前,设置状态为false,即复位。
为什么复位,因为我们处理当前线程的权限应该由线程本身来控制,如果不复位的话,下次循环就会终止,就相当于线程的终止由线程外部来控制了。
线程终止的本质
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();
}
private native void interrupt0();
我们可以看到,Thread的interrupt
方法,最终是调动了一个native的interrupt0
方法。
而在JVM中,我们可以看到 interrupt0 做了什么。
我们先来看JVM的 jvm.cpp:
JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_Interrupt");
// Ensure that the C++ Thread and OSThread structures aren't freed before we operate
oop java_thread = JNIHandles::resolve_non_null(jthread);
MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
// We need to re-resolve the java_thread, since a GC might have happened during the
// acquire of the lock
JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
if (thr != NULL) {
Thread::interrupt(thr);
}
JVM_END
其中最后的 Thread::interrupt(thr) 调用了操作系统层面的中断。
我们找到linux对应的os_linux.cpp
文件:
进入文件找到interrupt方法如下:
// interrupt support
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
if (!osthread->interrupted()) {
osthread->set_interrupted(true);//设置一个中断状态
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;//如果是sleep中,唤醒
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
上面代码是os的interrupt逻辑,会发现,它不仅仅是设置来一个interrupted变量值,
而且还有 upark的唤醒逻辑。
interrupt()
- 设置一个共享变量的值 true
- 唤醒处于阻塞状态下的线程