关于java:面试官问如何中断一个线程具体如何实现

6次阅读

共计 10804 个字符,预计需要花费 28 分钟才能阅读完成。

中断线程

线程的 thread.interrupt() 办法是中断线程,将会设置该线程的中断状态位,即设置为 true,中断的后果线程是死亡、还是期待新的工作或是持续运行至下一步,就取决于这个程序自身。线程会不断地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为 true)。它并不像 stop 办法那样会中断一个正在运行的线程。

判断线程是否被中断

判断某个线程是否已被发送过中断请求,请应用 Thread.currentThread().isInterrupted() 办法(因为它将线程中断标示位设置为 true 后,不会立即革除中断标示位,即不会将中断标设置为 false),而不要应用thread.interrupted()(该办法调用后会将中断标示位革除,即从新设置为 false)办法来判断,上面是线程在循环中时的中断形式:

while(!Thread.currentThread().isInterrupted() && more work to do){do more work}

如何中断线程

如果一个线程处于了阻塞状态(如线程调用了 thread.sleepthread.jointhread.wait、1.5 中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在查看中断标示时如果发现中断标示为 true,则会在这些阻塞办法(sleep、join、wait、1.5 中的condition.await 及可中断的通道上的 I/O 操作方法)调用处抛出 InterruptedException 异样,并且在抛出异样后立刻将线程的中断标示位革除,即从新设置为 false。抛出异样是为了线程从阻塞状态醒过来,并在完结线程前让程序员有足够的工夫来解决中断请求。

注,synchronized 在获锁的过程中是不能被中断的 ,意思是说如果产生了死锁,则不可能被中断(请参考前面的测试例子)。与 synchronized 性能类似的reentrantLock.lock() 办法也是一样,它也不可中断的,即如果产生死锁,那么 reentrantLock.lock() 办法无奈终止,如果调用时被阻塞,则它始终阻塞到它获取到锁为止。然而如果调用带超时的 tryLock 办法 reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在期待时被中断,将抛出一个 InterruptedException 异样,这是一个十分有用的个性,因为它容许程序突破死锁。你也能够调用 reentrantLock.lockInterruptibly() 办法,它就相当于一个超时设为有限的 tryLock 办法。

没有任何语言方面的需要一个被中断的线程应该终止。中断一个线程只是为了引起该线程的留神,被中断线程能够决定如何应答中断。某些线程十分重要,以至于它们应该不理睬中断,而是在解决完抛出的异样之后继续执行,然而更广泛的状况是,一个线程将把中断看作一个终止申请,这种线程的 run 办法遵循如下模式:

public void run() {
    try {
        ...
        /*
         * 不论循环里是否调用过线程阻塞的办法如 sleep、join、wait,这里还是须要加上
         * !Thread.currentThread().isInterrupted()条件,尽管抛出异样后退出了循环,显
         * 得用阻塞的状况下是多余的,但如果调用了阻塞办法但没有阻塞时,这样会更平安、更及时。*/
        while (!Thread.currentThread().isInterrupted()&& more work to do) {do more work}
    } catch (InterruptedException e) {// 线程在 wait 或 sleep 期间被中断了} finally {// 线程完结前做一些清理工作}
}

下面是 while 循环在 try 块里,如果 try 在 while 循环里时,因该在 catch 块里从新设置一下中断标示,因为抛出 InterruptedException 异样后,中断标示位会主动革除,此时应该这样:

public void run() {while (!Thread.currentThread().isInterrupted()&& more work to do) {
        try {
            ...
            sleep(delay);
        } catch (InterruptedException e) {Thread.currentThread().interrupt();// 从新设置中断标示}
    }
}

底层中断异样解决形式

另外不要在你的底层代码里捕捉 InterruptedException 异样后不解决,会处理不当,如下:

void mySubTask(){
    ...
    try{sleep(delay);
    }catch(InterruptedException e){}// 不要这样做
    ...
}

如果你不晓得抛 InterruptedException 异样后如何解决,那么你有如下好的倡议解决形式:

1、在 catch 子句中,调用 Thread.currentThread.interrupt() 来设置中断状态(因为抛出异样后中断标示会被革除),让外界通过判断 Thread.currentThread().isInterrupted() 标示来决定是否终止线程还是继续下去,应该这样做:

void mySubTask() {
    ...
    try {sleep(delay);
    } catch (InterruptedException e) {Thread.currentThread().isInterrupted();}
    ...
}

2、或者,更好的做法就是,不应用 try 来捕捉这样的异样,让办法间接抛出:

void mySubTask() throws InterruptedException {
    ...
    sleep(delay);
    ...
}

中断利用

应用中断信号量中断非阻塞状态的线程

中断线程最好的,最受举荐的形式是,应用共享变量(shared variable)发出信号,通知线程必须进行正在运行的工作。线程必须周期性的核查这一变量,而后有秩序地停止工作。Example2 形容了这一形式:

class Example2 extends Thread {
    volatile boolean stop = false;// 线程中断信号量

    public static void main(String args[]) throws Exception {Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 设置中断信号量
        thread.stop = true;
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        // 每隔一秒检测一下中断信号量
        while (!stop) {System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            /*
             * 应用 while 循环模仿 sleep 办法,这里不要应用 sleep,否则在阻塞时会 抛
             * InterruptedException 异样而退出循环,这样 while 检测 stop 条件就不会执行,* 失去了意义。*/
            while ((System.currentTimeMillis() - time < 1000)) {}}
        System.out.println("Thread exiting under request...");
    }
}

应用 thread.interrupt()中断非阻塞状态线程

尽管 Example2 该办法要求一些编码,但并不难实现。同时,它给予线程机会进行必要的清理工作。这里需注意一点的是需将共享变量定义成 volatile 类型或将对它的所有拜访封入同步的块 / 办法(synchronized blocks/methods)中。下面是中断一个非阻塞状态的线程的常见做法,但对非检测 isInterrupted() 条件会更简洁:

class Example2 extends Thread {public static void main(String args[]) throws Exception {Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 收回中断请求
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        // 每隔一秒检测是否设置了中断标示
        while (!Thread.currentThread().isInterrupted()) {System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            // 应用 while 循环模仿 sleep
            while ((System.currentTimeMillis() - time < 1000) ) {}}
        System.out.println("Thread exiting under request...");
    }
}

到目前为止一切顺利!然而,当线程期待某些事件产生而被阻塞,又会产生什么?当然,如果线程被阻塞,它便不能核查共享变量,也就不能进行。这在许多状况下会产生,例如调用 Object.wait()ServerSocket.accept()DatagramSocket.receive()时,这里仅举出一些。

他们都可能永恒的阻塞线程。即便产生超时,在超时期满之前继续期待也是不可行和不适当的,所以,要应用某种机制使得线程更早地退出被阻塞的状态。上面就来看一下中断阻塞线程技术。

应用 thread.interrupt()中断阻塞状态线程

Thread.interrupt()办法不会中断一个正在运行的线程。这一办法实际上实现的是,设置线程的中断标示位,在线程受到阻塞的中央(如调用 sleep、wait、join 等中央)抛出一个异样 InterruptedException,并且中断状态也将被革除,这样线程就得以退出阻塞的状态。上面是具体实现:

class Example3 extends Thread {public static void main(String args[]) throws Exception {Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.interrupt();// 等中断信号量设置后再调用
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {while (!Thread.currentThread().isInterrupted()) {System.out.println("Thread running...");
            try {
                /*
                 * 如果线程阻塞,将不会去查看中断信号量 stop 变量,所 以 thread.interrupt()
                 * 会使阻塞线程从阻塞的中央抛出异样,让阻塞线程从阻塞状态逃离进去,并
                 * 进行异样块进行 相应的解决
                 */
                Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异样
            } catch (InterruptedException e) {System.out.println("Thread interrupted...");
                /*
                 * 如果线程在调用 Object.wait()办法,或者该类的 join()、sleep()办法
                 * 过程中碰壁,则其中断状态将被革除
                 */
                System.out.println(this.isInterrupted());// false

                // 中不中断由本人决定,如果须要真真中断线程,则须要从新设置中断位,如果
                // 不须要,则不必调用
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

一旦 Example3 中的 Thread.interrupt() 被调用,线程便收到一个异样,于是逃离了阻塞状态并确定应该进行。下面咱们还能够应用共享信号量来替换 !Thread.currentThread().isInterrupted() 条件,但不如它简洁。

死锁状态线程无奈被中断

Example4 试着去中断处于死锁状态的两个线程,但这两个线都没有收到任何中断信号(抛出异样),所以 interrupt() 办法是不能中断死锁线程的,因为锁定的地位根本无法抛出异样:

class Example4 extends Thread {public static void main(String args[]) throws Exception {final Object lock1 = new Object();
        final Object lock2 = new Object();
        Thread thread1 = new Thread() {public void run() {deathLock(lock1, lock2);
            }
        };
        Thread thread2 = new Thread() {public void run() {
                // 留神,这里在替换了一下地位
                deathLock(lock2, lock1);
            }
        };
        System.out.println("Starting thread...");
        thread1.start();
        thread2.start();
        Thread.sleep(3000);
        System.out.println("Interrupting thread...");
        thread1.interrupt();
        thread2.interrupt();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    static void deathLock(Object lock1, Object lock2) {
        try {synchronized (lock1) {Thread.sleep(10);// 不会在这里死掉
                synchronized (lock2) {// 会锁在这里,尽管阻塞了,但不会抛异样
                    System.out.println(Thread.currentThread());
                }
            }
        } catch (InterruptedException e) {e.printStackTrace();
            System.exit(1);
        }
    }
}

中断 I / O 操作

然而,如果线程在 I / O 操作进行时被阻塞,又会如何?I/ O 操作能够阻塞线程一段相当长的工夫,特地是牵扯到网络应用时。例如,服务器可能须要期待一个申请(request),又或者,一个网络应用程序可能要期待远端主机的响应。

实现此 InterruptibleChannel 接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作个别有这些:serverSocketChannel.accept()socketChannel.connectsocketChannel.opensocketChannel.readsocketChannel.writefileChannel.readfileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 办法,这将导致该通道被敞开,并且已阻塞线程接将会收到 ClosedByInterruptException,并且设置已阻塞线程的中断状态。

另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将敞开并且该线程立刻接管到 ClosedByInterruptException;并依然设置其中断状态。如果状况是这样,其代码的逻辑和第三个例子中的是一样的,只是异样不同而已。

如果你正应用通道(channels)(这是在 Java 1.4 中引入的新的 I /O API),那么被阻塞的线程将收到一个 ClosedByInterruptException 异样。然而,你可能正应用 Java1.0 之前就存在的传统的 I /O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。Example5 形容了这一行为。只管 interrupt() 被调用,线程也不会退出被阻塞状态,比方 ServerSocket 的 accept 办法基本不抛出异样。

很侥幸,Java 平台为这种情景提供了一项解决方案,即调用阻塞该线程的套接字的 close() 办法。在这种情景下,如果线程被 I / O 操作阻塞,当调用该套接字的 close 办法时,该线程在调用 accept 地办法将接管到一个 SocketException(SocketException 为 IOException 的子异样)异样,这与应用 interrupt() 办法引起一个 InterruptedException 异样被抛出十分类似,(注,如果是流因读写阻塞后,调用流的 close 办法也会被阻塞,基本不能调用,更不会抛 IOExcepiton,此种状况下怎么中断?我想能够转换为通道来操作流能够解决,比方文件通道)。上面是具体实现:

class Example6 extends Thread {
    volatile ServerSocket socket;

    public static void main(String args[]) throws Exception {Example6 thread = new Example6();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        Thread.currentThread().interrupt();// 再调用 interrupt 办法
        thread.socket.close();// 再调用 close 办法
        try {Thread.sleep(3000);
        } catch (InterruptedException e) { }
        System.out.println("Stopping application...");
    }

    public void run() {
        try {socket = new ServerSocket(8888);
        } catch (IOException e) {System.out.println("Could not create the socket...");
            return;
        }
        while (!Thread.currentThread().isInterrupted()) {System.out.println("Waiting for connection...");
            try {socket.accept();
            } catch (IOException e) {System.out.println("accept() failed or interrupted...");
                Thread.currentThread().interrupt();// 从新设置中断标示位
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

一、没有任何语言方面的需要一个被中断的线程应该终止。中断一个线程只是为了引起该线程的留神,被中断线程能够决定如何应答中断。

二、对于处于 sleep,join 等操作的线程,如果被调用 interrupt() 后,会抛出 InterruptedException,而后线程的中断标记位会由 true 重置为 false,因为线程为了解决异样曾经从新处于就绪状态。

三、不可中断的操作,包含进入 synchronized 段以及 Lock.lock()inputSteam.read() 等,调用 interrupt() 对于这几个问题有效,因为它们都不抛出中断异样。如果拿不到资源,它们会无限期阻塞上来。

对于Lock.lock(),能够改用Lock.lockInterruptibly(),可被中断的加锁操作,它能够抛出中断异样。等同于等待时间有限长的Lock.tryLock(long time, TimeUnit unit)

对于 inputStream 等资源,有些 (实现了 interruptibleChannel 接口) 能够通过 close() 办法将资源敞开,对应的阻塞也会被放开。

首先,看看 Thread 类里的几个办法:

下面列出了与中断无关的几个办法及其行为,能够看到 interrupt 是中断线程。如果不理解 Java 的中断机制,这样的一种解释极容易造成误会,认为调用了线程的 interrupt 办法就肯定会中断线程。

其实,Java 的中断是一种合作机制。也就是说调用线程对象的 interrupt 办法并不一定就中断了正在运行的线程,它只是要求线程本人在适合的机会中断本人。每个线程都有一个 boolean 的中断状态(这个状态不在 Thread 的属性上),interrupt 办法仅仅只是将该状态置为 true。

比方对失常运行的线程调用 interrupt() 并不能终止他,只是扭转了 interrupt 标示符。

一般说来,如果一个办法申明抛出 InterruptedException,示意该办法是可中断的, 比方 wait,sleep,join,也就是说可中断办法会对 interrupt 调用做出响应(例如 sleep 响应 interrupt 的操作包含革除中断状态,抛出 InterruptedException), 异样都是由可中断办法本人抛出来的,并不是间接由 interrupt 办法间接引起的。

Object.wait, Thread.sleep办法,会一直的轮询监听 interrupted 标记位,发现其设置为 true 后,会进行阻塞并抛出 InterruptedException 异样。

看了以上的阐明,对 java 中断的应用必定是会了,但我想晓得的是阻塞了的线程是如何通过 interuppt 办法实现进行阻塞并抛出 interruptedException 的,这就要看 Thread 中 native 的 interuppt0 办法了。

第一步学习 Java 的 JNI 调用 Native 办法。

第二步下载 openjdk 的源代码,找到目录构造里的 openjdk-src\jdk\src\share\native\java\lang\Thread.c 文件。

#include "jni.h"
#include "jvm.h"

#include "java_lang_Thread.h"

#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};

#undef THD
#undef OBJ
#undef STE
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

作者:零_壹 \
起源:https://blog.csdn.net/xinxiao…

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.20w 程序员红包封面,快快支付。。。

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0