zoukankan      html  css  js  c++  java
  • 【Java】Java线程中断(Interrupt)与阻塞(park)的区别

    对于很多刚接触编程的人来说,对于线程中断和线程阻塞两个概念,经常性是混淆起来用,单纯地认为线程中断与线程阻塞的概念是一致的,都是值线程运行状态的停止。其实这个观点是错误的,两者之前有很大的区别,下文就着重介绍两者之间的区别。

    线程中断

    在一个线程正常结束之前,如果被强制终止,那么就有可能造成一些比较严重的后果,设想一下如果现在有一个线程持有同步锁,然后在没有释放锁资源的情况下被强制休眠,那么这就造成了其他线程无法访问同步代码块。因此我们可以看到在 Java 中类似 Thread#stop() 方法被标为 @Deprecated

    针对上述情况,我们不能直接将线程给终止掉,但有时又必须将让线程停止运行某些代码,那么此时我们必须有一种机制让线程知道它该停止了。Java 为我们提供了一个比较优雅的做法,即可以通过 Thread#interrupt() 给线程该线程一个标志位,让该线程自己决定该怎么办。

    接下来就用代码来延时下 interrupt() 的作用:

    public class InterruptDemo {
    
        static class MyThread implements Runnable {
            @Override
            public void run() {
                for (int i= 0; !Thread.currentThread().isInterrupted() && i < 200000; i++) {
                    System.out.println(Thread.currentThread().getName() + ":i = " + i);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread myThread = new Thread(new MyThread());
            myThread.start();
    
            // 让线程运行一段时间
            Thread.sleep(5);
    
            myThread.interrupt();
    
            // 等待 myThread 运行停止
            myThread.join();
            System.out.println("end");
        }
    
    }
    

    以上代码的运行结果如下:

    可以看到,当前线程并没有按 for 循环中的结束量 20000 去跑,而是在被中断后,停止了当前了 for 循环。所以我们可以利用 interrupt 配置线程使用,使得线程在一定的位置停止下来。

    不过到这里可能会让人产生一些疑惑,因为在这里看起来,当前线程像是被阻塞掉了,其实并不是的,我们可以利用下面这段代码来演示下:

    public class InterruptDemo {
    
        static class MyThread implements Runnable {
            @Override
            public void run() {
                for (int i= 0; i < 200000; i++) {
                    System.out.println(Thread.currentThread().getName() + ":i = " + i);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread myThread = new Thread(new MyThread());
            myThread.start();
    
            // 让线程运行一段时间
            Thread.sleep(5);
    
            myThread.interrupt();
    
            // 等待 myThread 运行停止
            myThread.join();
            System.out.println("end");
        }
    
    }
    

    上面这段代码的运行结果如下:

    可见,线程一直打印到 20000,执行完毕后推出线程,并没有像我们预料中在某处中断。所以我们可以得出结论:单纯用 interrupt() 中断线程方法并不能停止当前正在运行的线程,需要配合其他方法才能正确停止线程。

    了解完中断的基本概念后,线程的中断还有需要其他需要注意的点:

    • 设置线程中断后,线程内调用 wait()join()slepp() 方法中的一种,都会抛出 InterruptedException 异常,且中断标志位被清除,重新设置为 false;
    • 当线程被阻塞,比如调用了上述三个方法之一,那么此时调用它的 interrupt() 方法,也会产生一个 InterruptedException 异常。因为没有占有 CPU 的线程是无法给自己设置中断状态位置的;
    • 尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式:tryLock(long time, TimeUnit unit)
    • 当代码调用中需要抛出一个 InterruptedException,捕获之后,要么继续往上抛,要么重置中断状态,这是最安全的做法。

    线程阻塞

    上面讲完了线程中断,它其实只是一个标志位,并不能让线程真正的停止下来,那么接下来就来介绍如何真正让线程停止下来。

    对于这个问题,Java 中提供了一个较为底层的并发工具类:LockSupport,该类中的核心方法有两个:park(Object blocker) 以及 unpark(Thread thred),前者表示阻塞指定线程,后者表示唤醒指定的线程。

    // java.util.concurrent.locks.LockSupport
    
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    

    该方法在 Java 的语言层面上比较简单,最终也是去调用 UNSAFE 中的 native 方法。真正涉及到底层的东西需要去理解 JVM 的源码,这里就不做太多的介绍。不过我们可以用一个简单的例子来演示下这两个方法:

    public class LockSupportDemo {
    
        static class MyThread implements Runnable {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "执行结束");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread thread = new Thread(new MyThread(), "线程:MyThread");
            thread.start();
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "主线程执行中");
            LockSupport.unpark(thread);
            System.out.println(Thread.currentThread().getName() + "主线程执行结束");
        }
    
    }
    

    上述代码的执行结果为:

    线程:MyThread开始执行
    main主线程执行中
    线程:MyThread执行结束
    main主线程执行结束
    

    可以看到,myThread 线程在开始执行后停止了下来,等到主线程重新调用 LockSupport.unpark(thread) 后才重新开始执行。

  • 相关阅读:
    利用.Net的CLR和BCL实现函数IsNumeric
    30岁前挣到10万年薪 五位年轻人的高薪秘诀
    三级关链菜单通用版
    对Session和Cookie的区分与理解
    转贴:C#排序算法大全
    无效的 CurrentPageIndex 值.它必须大于等于 0 且小于 PageCount!的问题
    ASP.NET中“检测到有潜在危险的表单值”的解决方法
    如何让网页版面更适合浏览者呢?这里有技巧
    十二星座情侣恋爱积分
    asp.net 三种基本的参数传递方法(web forms)
  • 原文地址:https://www.cnblogs.com/jojop/p/13957027.html
Copyright © 2011-2022 走看看