• JUC并发编程与源码分析笔记05-LockSupport与线程中断


    内容简介

    LockSupport

    LockSupport是java.util.concurrent.locks包下的一个类,可以理解成是对Lock类的扩展。

    线程中断机制

    java.lang.Thrread里有三个方法:interrupt()interrupted()isInterrupted()

    线程中断机制

    什么是中断机制

    一个线程不应该由其他线程来强制中断或停止,而是应该山线程自己自行停止,自己来决定自己的命运。所以,Thread.stop()Thread.suspend()Thread.resume()都已经被废弃了。
    在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制―—中断,也即中断标识协商机制。
    中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
    若要中断一个线程,你需要手动调用该线程的interrupt()方法,该方法也仅仅是将线程对象的中断标识设成true,接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,
    此时究竟该做什么需要你自己写代码实现。
    每个线程对象中都有一个中断标识位,用于表示线程是否被中断,该标识位为true表示中断,为false表示未中断。通过调用线程对象的interrupt()方法将该线程的标识位设为true,可以在别的线程中调用,也可以在自己的线程中调用。

    中断的相关三大API方法说明

    public void interrupt():设置线程的中断状态为true,发起一个协商,不会立刻停止线程。
    public static boolean interrupted():判断线程是否被中断并清除当前中断状态。这个方法做了两件事:1.返回当前线程的中断状态,测试当前线程是否已被中断;2.将当前线程的中断状态清零并设置成false,清除线程的中断状态。
    如果连续两次调用此方法,则第二次调用将返回false ,因为连续调用两次的结果可能不一样。
    public boolean isInterrupted():通过检查中断标志位,判断当前线程是否被中断。

    大厂面试题中断机制考点

    如何停止中断运行中的线程

    通过一个volatile变量实现
    public class InterruptDemo {
        static volatile boolean stop = false;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (!stop) {
                    System.out.println(Thread.currentThread().getName() + "正在运行");
                }
            }, "thread1").start();
            Thread.sleep(10);
            new Thread(() -> {
                stop = true;
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    通过AtomicBoolean实现
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class InterruptDemo {
        static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (!atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + "正在运行");
                }
            }, "thread1").start();
            Thread.sleep(10);
            new Thread(() -> {
                atomicBoolean.set(true);
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    通过Thread类自带的中断api实例方法实现

    在需要中断的线程中,不断监听中断状态,一旦发生中断,就执行中断处理业务stop线程。

    public class InterruptDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "正在运行");
                }
            }, "thread1");
            thread1.start();
            System.out.println("thread1默认标志位" + thread1.isInterrupted());
            Thread.sleep(10);
            new Thread(thread1::interrupt, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对一个线程调用interrupt()时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己配合才行。
    如果线程处于阻塞状态(sleep,wait,join等),在别的线程中调用当前线程对象的interrupt()方法,线程将理解退出被阻塞状态,并抛出一个InterrException异常。

    当前线程中断标识为true,是不是线程就立刻停止

    public class InterruptDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 300;i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
                System.out.println("thread1调用interrupt()后标志位2:" + Thread.currentThread().isInterrupted());
            }, "thread1");
            thread1.start();
            System.out.println("thread1默认标志位:" + thread1.isInterrupted());
            Thread.sleep(2);
            thread1.interrupt();
            System.out.println("thread1调用interrupt()后标志位1:" + thread1.isInterrupted());
            Thread.sleep(2000);
            System.out.println("thread1调用interrupt()后标志位3:" + thread1.isInterrupted());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class InterruptDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println(Thread.currentThread().getName() + "中断标志位:" + Thread.currentThread().isInterrupted() + "程序停止");
                        break;
                    }
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        // 如果没有这句,thread1可能一直执行,当thread1处于阻塞状态的时候,其他线程调用了thread1.interrupt(),就会抛异常,而且会清空标志位的值
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                    System.out.println("hello world");
                }
            }, "thread1");
            thread1.start();
            Thread.sleep(1000);
            thread1.interrupt();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    静态方法Thread.interrupted(),谈谈你的理解

    判断线程是否被中断并清除当前中断状态。
    这个方法做了两件事:
    返回当前线程中断状态,测试当前线程是否已被中断
    将当前线程中断状态清零并设置为false,清除线程的中断状态

    public class InterruptDemo {
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
            System.out.println(1);
            Thread.currentThread().interrupt();
            System.out.println(2);
            // 因为执行了Thread.currentThread().interrupt();所以返回true,并且Thread.interrupted()还会把中断标志位置为false
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看源码,就会发现isInterrupted()interrupted()最终都调用了private native boolean isInterrupted(boolean ClearInterrupted);方法,不同点是传参不同,也就是说,这次操作是否需要清空标志位。

    LockSupport是什么

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport中park()unpark()的作用分别是阻塞线程和解除阻塞线程。

    线程等待唤醒机制

    3种让线程等待和唤醒的方法

    1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
    2. 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
    3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

    Object类中wait和notify方法实现线程等待和唤醒

    public class LockSupportDemo {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {
                        object.wait();// 当前进程释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "被唤醒");
                }
            }, "thread1").start();
            Thread.sleep(1000);
            new Thread(() -> {
                synchronized (object) {
                    object.notify();
                    System.out.println(Thread.currentThread().getName() + "发出通知");
                }
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    两个异常情况:
    如果把上面代码的两个synchronized代码块注释掉,再次运行,程序会报错。结论:wait()notify()方法必须在synchronized里,也就必须持有该对象的锁,才能调用该对象的wait()notify()方法。
    尝试改变两个线程wait()notify()的顺序,也就是在thread1里wait之前,加一个sleep,先让thread2执行notify,再让thread1执行wait,程序在走完wait()后,是没法被唤醒的,永远wait下去,程序不能正常退出。结论:必须先调用wait(),再调用notify()

    Condition接口中的await后signal方法实现线程的等待和唤醒

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockSupportDemo {
        public static void main(String[] args) throws InterruptedException {
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒");
            }, "thread1").start();
            Thread.sleep(1000);
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "发出通知");
                } finally {
                    lock.unlock();
                }
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    两个异常情况:
    lock.lock()lock.unlock()注释掉,再次运行,程序会报错。结论:await()signal()的执行,需要先获得锁。
    尝试改变await()signal()的执行顺序,同样,也会出现程序不能正常停止的情况。结论:必须先调用await(),再调用signal()

    上述两个对象Object和Condition使用的限制条件

    1. 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
    2. 必须先等待后唤醒,线程才能被成功唤醒

    LockSupport类中的park等待和unpark唤醒

    是什么

    通过park()unpark(thread)方法实现阻塞和唤醒线程的操作。

    主要方法

    阻塞:public static void park()
    唤醒:public static void unpark(Thread thread)

    代码

    import java.util.concurrent.locks.LockSupport;
    
    public class LockSupportDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "被唤醒");
            }, "thread1");
            thread1.start();
            Thread.sleep(1000);
            new Thread(() -> {
                LockSupport.unpark(thread1);
                System.out.println(Thread.currentThread().getName() + "发出通知");
            }, "thread2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    尝试之前的两个异常情况,发现程序可以正常执行。
    使用LockSupport后,不需要把阻塞和唤醒放在锁内部,不需要控制阻塞和唤醒的顺序。
    对于LockSupport.park()LockSupport.unpark(),可以理解为通行证的意思,park()是上交通行证,unpark()是发放通行证,而且通行证在手里最多只能有一张。
    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
    LockSupport提供park()unpark()方法实现阻塞线程和解除线程阻塞的过程LockSupport和每个使用它的线程都有一个许可关联。
    每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark()也不会积累凭证。
    线程阻塞需要消耗凭证,这个凭证最多只有一个。
    当调用park()方法时,直接消耗掉这个凭证然后正常退出,如果没有凭证,就必须阻塞等待凭证可用。
    当调用unpark()方法时,会增加一个凭证,但是凭证最多只能有一个,累计无效。

  • 相关阅读:
    负载均衡反向代理下的webshell上传
    使用docker部署mysql8.0+zabbix5.0
    Ubuntu设置允许root用户登录
    2023年亚太杯数学建模思路 - 案例:感知机原理剖析及实现
    NJ 时钟自动调整功能(SNTP)
    路由套接字
    巧用网络分析仪的校准
    【Java开发】 Spring 04:云服务器 Docker 环境下安装 Redis 并连接 Spring 项目实现简单 CRUD
    如何查找特定基因集合免疫基因集 炎症基因集
    MD5加密算法
  • 原文地址:https://blog.csdn.net/qq_36059561/article/details/128089612