Java-并发-Java线程中断与停止线程详解

线程中断

  Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。即“线程中断”并不是字面意思——线程真的中断了,而是设置了中断标志位为true。

thread.interrupt()

  该方法“中断线程”,但仅仅是会设置该线程的中断状态位为true,至于中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。
  线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会立即中断一个正在运行的线程,因此没有stop()方法带来的的问题。
  如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、condition.await、以及可中断的通道上的 I/O 操作等方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
  synchronized在获锁的过程中的阻塞态是不能被中断的(可以设置标志位为true,但是不会抛出异常,因此不能从阻塞态中返回),与synchronized功能相似的lock.lock()方法也是一样,它也不可中断的,即如果发生死锁,意思是说如果产生了死锁,则不可能中断某个发生死锁而处于等待的线程。但是如果调用带超时的tryLock方法lock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用lock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
  有一个特例是:被LockSupport.park()阻塞的线程也可以被中断,但是不会抛出异常.并且不会恢复标志位。

thread.isInterrupted()

  如果要检测当前线程是否被中断,请使用该方法,因为它不会清除中断标示位,即不会将中断标设置为false,就仅仅是检测状态而已。
  

public boolean isInterrupted() {return isInterrupted(false);}
// ClearInterrupted  false不重置标志位;true 重置标志位
private native boolean isInterrupted(boolean ClearInterrupted);

  判断是否中断还有一个方法Thread.interrupted(),但是请谨慎使用。该方法调用后会将中断标示位清除,即重新设置为false。并且该方法是Thread类的静态方法。

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

补充:
  interrupted()内部是获取当前调用线程的中断标志,而isInterrupted()则是获取调用isInterrupted()方法的实例对象的中断标志。下面验证:

public class interrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {}
        });
        thread.start();
        thread.interrupt();
        //获取中断标志,获取子线程的中断标志
        System.out.println("isInterrupted:" + thread.isInterrupted());
        //获取中断标志并重置,虽然是thread.interrupted(),但实际上是获取主线程的中断标志,因为在主线程中调用的
        System.out.println("interrupted:" + thread.interrupted());
        //获取中断标志并重置,也是获取主线程的中断标志
        System.out.println("interrupted:" + Thread.interrupted());
        //获取中断标志,获取子线程的中断标志
        System.out.println("interrupted:" + thread.isInterrupted());
    }
}

结果是

isInterrupted:true
interrupted:false
interrupted:false
interrupted:true

应用

  当线程为了等待一些特定条件的到来时,一般会调用sleep函数、wait 系列函数或者join ()函数来阻塞挂起当前线程。比如一个线程调用了Thread.sleep(3000),那么调用线程会被阻塞,直到3s 后才会从阻塞状态变为激活状态。但是有可能在3s 内条件己被满足,如果一直等到3s后再返回有点琅费时间,这时候可以调用该线程的interrupt()方法, 强制sleep 方法抛出InterruptedException 异常而返回,线程恢复到激活状态。

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Interrupt interrupt = new Interrupt();
        Thread thread1 = new Thread(interrupt, "thread1");
        Thread thread2 = new Thread(interrupt, "thread2");
        thread1.start();
        //主线程睡眠一秒,等待thread1获得锁,再开启thread2
        Thread.currentThread().sleep(1000);
        thread2.start();
        //主线程再次睡眠一秒,等待thread2由于获取不到锁而处于BLOCKED阻塞态(阻塞在synchronized处)
        Thread.currentThread().sleep(1000);
        //获取thread2状态
        System.out.println(thread2.getName() + "状态:" + thread2.getState());
        //尝试设置thread2的中断状态
        thread2.interrupt();
        //获取是否thread2是否处于中断,返回true,说明是,
        //但是没有抛出异常,此时我们便没办法处理这个出于阻塞态的线程
        System.out.println(thread2.getName() + "是否是中断状态: " + thread2.isInterrupted());
        System.out.println(thread2.getName() + "由于处于synchronized阻塞态,即使除以中断态,也没有抛出异常,无法结束");

        System.out.println();
        System.out.println("=========>开始处理这个问题");
        System.out.println();


        //获取thread1的状态,可以发现是TIMED_WAITING.并且是采用Thread.sleep(100)方式,可以被中断并抛出异常
        System.out.println(thread1.getName() + "状态:" + thread1.getState());
        //尝试设置thread1的中断状态
        thread1.interrupt();
        System.out.println(thread1.getName() + "是否是中断状态: " + thread1.isInterrupted());

        //由于thread1 中断了等待,处理了异常,结束了运行,释放了锁。
        //thred2获得了锁,进入同步块,执行sleep方法进入TIMED_WAITING状态
        //由于thread2在前面设置了中断状态,因此也立即从sleep方法出抛出异常,并且正常结束线程。
    }

    static class Interrupt implements Runnable {
        @Override
        public void run() {
            synchronized (Thread.class) {
                try {
                    //处于等待  该等待方法可被中断,此时会中断等待,并且抛出异常,程序可以结束.
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    //cache处理中断异常,结束等待,是的线程能正常结束运行
                    System.out.println(Thread.currentThread().getName() + "抛出了异常,结束了TIMED_WAITING,该线程可以返回");
                }
            }

        }
    }
}

停止线程

  这里的停止线程就是指结束线程。常见方法有以下几种

  1. 正常运行结束 程序运行结束,线程自动结束。
  2. stop方法:但是已经过时,容易引发死锁。
      即刻停止run()方法中剩余的全部工作,包括在catch或finally语句中(如果不在try块中)并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
      会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。如果以前受这些监视器保护的任何对象都处于不连贯状态,那么损坏的对象对其他线程可见,这有可能导致不安全的操作。
  3. 通过修改某个变量来终止线程。
      即在线程当中,定义一个标志位,通过改变标志位的形式,结束线程。
  4. thread.interrupt()
      使用interrupt方法可以中断线程的等待状态,并不是直接把线程终止。我们可以配合抛出的异常来停止部分等待状态的线程
      如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException——当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。我们可以在catch当中捕获到异常,并且改变标志位,或者跳出循环,即可结束线程。
      如果想要中断等待获得锁的线程的等待状态,那么该方法需要配合lock.lockInterruptibly()或者trylock()获取锁的方法!因为这种获取锁的方式允许等待锁的线程被中断。如果使用lock()方法,那么interrupt()方法无效,无法中断因为获取不到锁而等待(受阻)的线程,synchronized等待也是无法中断的。
发布了29 篇原创文章 · 获赞 47 · 访问量 8199

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/104933454