• LockSupport与线程中断


    LockSupport与线程中断

    线程中断机制

    image-20221201201022625

    voidinterrupt()中断此线程
    static booleaninterrupted()获取当前线程中断标志位 true|false
    booleanisInterrupted()获取当前线程中断标志位true|false

    static boolean interrupted()和boolean isInterrupted()的区别

    1.一个静态方法一个是实例方法

    2.静态方法会清除线程中断标志位,置为默认值false

    3.实例方法不会清除线程终端标志位

    什么是中断机制?

    • 首先
      一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。

    所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

    • 其次
      在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。

    因此,Java提供了一种用于停止线程的协商机制——中断。

    中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

    若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;

    接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,

    此时究竟该做什么需要你自己写代码实现。

    每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;

    通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

    • eg.顾客在无烟餐厅中吸烟,服务员希望他别吸烟了,不是强行停止他吸烟,而是给他的标志位打为true,具体的停止吸烟还是要顾客自己停止。(体现了协商机制)

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

    image-20221201201842901

    public void interrupt()实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
    public static boolean interrupted()静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:1 返回当前线程的中断状态2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)
    public boolean isInterrupted()实例方法,判断当前线程是否被中断(通过检查中断标志位)

    大厂面试题:如何使用中断标识停止线程?

    1如何停止中断运行中的线程?
    ① 通过一个volatile变量实现
    • volatile修饰的遍历线程共享,保证了可见性,t2修改了标志位后能马上被t1看到
    public class interruptDemo {
        static volatile boolean isStop = false;
    
        public static void main(String[] args) {
            new Thread(() -> {
                while (true) {
                    if (isStop) {//如果这个标志位被其他线程改为true了
                        System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序终止");
                        break;
                    }
                    System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印
                }
            }, "t1").start();
    
            try {
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                isStop = true;
            }, "t2").start();
        }
    }
    //--
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1   isStop被修改为true,程序终止
    
    • 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
    • 33
    • 34
    ② 通过AtomicBoolean(原子布尔型)
    public class interruptDemo {
    
        static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    
        public static void main(String[] args) {
            m1_volatile();
        }
    
        public static void m1_volatile() {
            new Thread(()->{
                while(true){
                    if(atomicBoolean.get()){//如果这个标志位被其他线程改为true了
                        System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序终止");
                        break;
                    }
                    System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印
                }
            },"t1").start();
    
            try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
    
            new Thread(()->{
                atomicBoolean.set(true);
            },"t2").start();
        }
    }
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1 ------hello volatile
    //t1   isStop被修改为true,程序终止
    
    • 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
    • 33
    ③ 通过Thread类自带的中断api方法实现
    public class interruptDemo {
        //默认的中断标志位是false,然后被改为了true
        public static void main(String[] args) {
            m1_volatile();
        }
    
        public static void m1_volatile() {
            Thread t1 = new Thread(() -> {
                while (true) {
                    //默认的线程中断标志位位false,当线程执行interrupt()方法后变为false
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止");
                        break;
                    }
                    System.out.println("t1 ------hello interrupt ");//----------------------如果没停止,那就一直打印
                }
            }, "t1");
            t1.start();
            
            //睡眠2毫秒在设置线程中断标志位
            try {
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                //把t1的中断标志位设置位true
                t1.interrupt();
            }, "t2").start();
        }
    }
    
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1 ------hello interrupt
    //t1   isInterrupted()被修改为true,程序终止
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    —API源码分析

    实例方法interrupt(),没有返回值

    //Thread.java
    public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
    
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag----调用了interrupt0()方法
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    //Thread.java
        /* Some private helper methods */
        private native void setPriority0(int newPriority);
        private native void stop0(Object o);
        private native void suspend0();
        private native void resume0();
        private native void interrupt0();  //---------------------------调用了底层JVM机
        private native void setNativeName(String name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    实例方法isInterrupted,返回布尔值

    //Thread.java
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    • 1
    • 2
    • 3
    • 4
    //Thread.java
    private native boolean isInterrupted(boolean ClearInterrupted);//也调用了c底层
    
    • 1
    • 2

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

      • 仅仅设置了一个中断状态
    • 看看中断是否会立即停止这个循环300次的线程
      • 否,虽然中断标志位变了。但是i一直在循环
    public class InterruptDemo02 {
        public static void main(String[] args) {
            Thread t1 = new Thread(()->{
                for(int i = 0;i < 300;i ++){
                    System.out.println("---------" + i);
                }
                System.out.println("after t1.interrupt()---第2次----"+Thread.currentThread().isInterrupted());
            },"t1");
            t1.start();
            System.out.println("before t1.interrupt()----"+t1.isInterrupted());
            t1.interrupt();
            try { TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
            System.out.println("after t1.interrupt()---第1次---"+t1.isInterrupted());
            try {TimeUnit.MILLISECONDS.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}
            System.out.println("after t1.interrupt()---第3次---"+t1.isInterrupted());
        }
    }
    //before t1.interrupt()----false
    //---------0
    //---------1
    //---------2
    //---------3
    //....
    //---------136
    //after t1.interrupt()---第1次---true    ------此处中断标志位设置为了true,但是t1仍然在运行
    //---------137
    //---------298
    //---------299
    //after t1.interrupt()---第2次----true
    //after t1.interrupt()---第3次---false//中断不活动的线程不会产生任何影响,线程结束后中断标识会变更位false
    
    • 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
    后手案例-深入
    • 在我们基本中断程序的骨架上 + 一个sleep阻塞
    • 中断异常 且 会导致程序无限循环.
    public class InterruptDemo03 {
        public static void main(String[] args) {
            Thread t1 =  new Thread(()->{
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println(Thread.currentThread().getName()+"\t"+
                                "中断标志位:"+Thread.currentThread().isInterrupted()+"程序终止");
                        break;
                    }
                    try {
                        //当线程中含有sleep、wait、join等方法,调用interrupt会抛出InterruptedException异常并将线程中断标志重置位false
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //因此需要再catch中再次调用interrupt方法将线程中断标识设置位true,循环才会停止
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("-----hello InterruptDemo03,当前线程中断标识为: "+Thread.currentThread().isInterrupted());
                }
            },"t1");
            //启动线程
            t1.start();
            try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
            new Thread(() -> t1.interrupt()).start();
        }
    }
    //抛出InterruptedException异常,但并且程序一直在跑
    //java.lang.InterruptedException: sleep interrupted
    //  at java.lang.Thread.sleep(Native Method)
    // -----hello InterruptDemo03,当前线程中断标识为: false
    // -----hello InterruptDemo03,当前线程中断标识为: false
    // -----hello InterruptDemo03,当前线程中断标识为: false
    // -----hello InterruptDemo03,当前线程中断标识为: false
    // -----hello InterruptDemo03,当前线程中断标识为: false
    //......
    //----------------------------
    //---------加了Thread.currentThread().interrupt();
    //java.lang.InterruptedException: sleep interrupted
    // at java.lang.Thread.sleep(Native Method)
    //  at com.zhang.admin.controller.InterruptDemo03.lambda$main$0(InterruptDemo03.java:15)
    //  at java.lang.Thread.run(Thread.java:748)
    //-----hello InterruptDemo03,当前线程中断标识为: true
    //t1  中断标志位:true程序终止
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 前文
      1. ② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。
    /**
     * 1 中断标志位 默认是false
     * 2 t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
     * 3 中断标志位true,正常情况下,程序停止,^-^
     * 4 中断标志位true,异常情况下,InterruptedException,将会把中断状态清除,并且将收到InterruptedException。中断标志位false导致无限循环。
     * 
     * 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch如果没有通过th.interrupt()方法再次将中断标志设置为true,这就导致无限循环了

    小总结
    • 中断只是一种协同机制,修改中断标识位仅此而已,而不是立刻stop打断线程

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

    • api里的第二个

      public static boolean interrupted()
      静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:

      1.返回当前线程的中断状态

      2.将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)

    public class InterruptDemo04 {
        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");
            //中断标志位设置为true
            Thread.currentThread().interrupt();
            System.out.println("-----2");
            //此时线程中断标志位已经被interrupt设置位了true,
            //第一次调用静态的interrupted获取到中断标志位为true,但静态的interrupted还会将线程终端标志位状态重置
            System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());
            //因此再次获取线程中断标志位变成了默认值false
            System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());
        }
    }
    //main  false
    //main  false
    //-----1
    //-----2
    //main  true
    //main  false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 看下源码,interrupted()对比isInterrupted()
    public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
        
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public boolean isInterrupted() {
            return isInterrupted(false);
        }
    
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ClearInterrupted标识是否清除当前线程中断状态,true-清除,false-不清除

    他们在底层都调用了native方法isInterrupted。只不过传入参数ClearInterrupted一个传参传了true,一个传了false。

    静态方法interrupted() 中true表示清空当前中断状态。

    实例方法isInterrupted 则不会。

    LockSupport是什么

    • 官方解释:用于创建锁和其他同步类的基本线程阻塞原语。

    核心就是park()unpark()方法

    • park()方法是阻塞线程
    • unpark()方法是解除阻塞线程

    image-20221202133408357

    线程等待唤醒机制

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

    1.使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

    2.使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

    3.LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

    image-20221202133638238

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

    • 正常
    public class LockSupportDemo {
        public static void main(String[] args) {
            Object objectLock = new Object();
    
            new Thread(() -> {
                synchronized (objectLock) {
                    System.out.println(Thread.currentThread().getName() + "\t ---- come in");
                    try {
                        objectLock.wait();//----------------------这里先让他等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                synchronized (objectLock) {
                    objectLock.notify();//-------------------------再唤醒它
                    System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                }
            }, "t2").start();
        }
    }
    //t1   ---- come in
    //t2   ---发出通知
    //t1  ---被唤醒了
    
    • 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
    • 33
    • 34

    程序执行objectLock.wait()t1线程进入等待,3秒之后t2线程执行objectLock.notify()唤醒线程,t1线程继续执行。

    • 异常1—去掉synchronized

    • 说明要使用waitnotify必须加synchronized

    public class LockSupportDemo {
        public static void main(String[] args) {
            Object objectLock = new Object();
    
            new Thread(() -> {
                //    synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t ---- come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //      }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                //     synchronized (objectLock) {
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                //     }
            }, "t2").start();
        }
    }
    //t1   ---- come in
    //Exception in thread "t1" java.lang.IllegalMonitorStateException
    //  at java.lang.Object.wait(Native Method)
    //  at java.lang.Object.wait(Object.java:502)
    //  at com.zhang.admin.controller.LockSupportDemo.lambda$main$0(LockSupportDemo.java:15)
    //  at java.lang.Thread.run(Thread.java:748)
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37

    去掉锁之后,会抛出IllegalMonitorStateException异常,Monitor是对象监视器。可以看出waitnotify必须加synchronized同步锁

    • 异常2—把notify和wait的执行顺序对换
    public class LockSupportDemo {
        public static void main(String[] args) {
            Object objectLock = new Object();
    
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objectLock) {
                    System.out.println(Thread.currentThread().getName() + "\t ---- come in");
                    try {
                        objectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
            //程序执行首先进入t1线程发现t1线程暂停1秒,
            //开始执行t2线程,t2线程执行了notify方法之后,t1再去执行wait()方法是无法唤醒t1线程的
            //因此t1线程会一直阻塞
            new Thread(() -> {
                synchronized (objectLock) {
                    objectLock.notify();//这个先执行了
                    System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                }
            }, "t2").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
    小总结
    • wait和notify方法必须要在同步块或者方法里面,且成对出现使用
    • 先wait后notify才OK,顺序

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

    • 正常
    public class LockSupportDemo {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t-----come in");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "\t -----被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").start();
    
        }
    }
    //t1  -----come in
    //t2  我要进行唤醒
    //t1   -----被唤醒
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    程序执行condition.await();t1线程进入等待,3秒之后t2线程执行condition.signal();唤醒线程,t1线程继续执行。

    • 异常1 去掉lock锁
      • 仍然返回IllegalMonitorStateException
    public class LockSupportDemo {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(() -> {
                // lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t-----come in");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "\t -----被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // lock.unlock();
                }
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                // lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").start();
    
        }
    }
    // Exception in thread "t1" java.lang.IllegalMonitorStateException
    // t2	我要进行唤醒
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    和object类中的wait和notify方法一样 await和signal必须加锁才能争取执行,注意线程t1和线程t2都是需要加锁的,否则会抛出IllegalMonitorStateException异常

    • 异常2 先执行signal()方法在执行await()方法
      • 仍然在不停的循环
    public class LockSupportDemo {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t-----come in");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "\t -----被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
    
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    小总结
    • awaitnotify类似于上面waitnotify
      • Condition中的线程等待和唤醒方法,需要先获取锁
      • 一定要先await后signal,不能反了

    Object和Condition使用的限制条件

    • 总结
      • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
      • 必须要先等待后唤醒,线程才能够被唤醒

    ③LockSupport类中的park等待和unpark唤醒是什么

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

    • 官网解释

      image-20221202140449130

      • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

      • LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),

      • permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒

      • 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。

    主要方法
    API

    image-20221202140643621

    阻塞
    • park()/park(Object blocker)
    • 调用LockSupport.park()时,发现它调用了unsafe类,并且默认传了一个0
    public static void park() {
         UNSAFE.park(false, 0L);
    }
    
    • 1
    • 2
    • 3

    image-20221202140752575

    • permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
      然后会将permit再次设置为零并返回。
    唤醒
    • 调用LockSupport.unpark();时,也调用了unsafe类
    public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
    }
    
    • 1
    • 2
    • 3
    • 4

    image-20221202140926143

    • 调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

    代码

    • 正常+无锁块要求
    public class LockSupportDemo {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t----------come in");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了");
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
        }
    }
    //t1  ----------come in
    //t2  -----发出通知,去唤醒t1
    //t1  ----------被唤醒了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 之前错误的先唤醒后等待,LockSupport照样支持
    public class LockSupportDemo {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis());
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis());
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
        }
    }
    //t2  -----发出通知,去唤醒t1
    //t1  ----------come in  1654750785663
    //t1  ----------被唤醒了  1654750785663
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的
    - 类似于高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。

    总结

    • 许可证是只要一个的
    public class LockSupportDemo {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis());
                LockSupport.park();
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis());
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
        }
    }
    //t2  -----发出通知,去唤醒t1
    //t1  ----------come in  1669961562405--------------------卡在这里了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    第一次upark(t1)发放了第一个LockSupport.park();的通行证,由于upark(t1)发放凭证只能有一个,所以第二次发放的还是同一个凭证,导致第二LockSupport.park()获取不到通行证,线程堵塞在了这里。因此upark和park是需要成对出现的,要一对一。

    • 小总结

    Lock Support是用来创建锁和其他同步类的基本线程阻塞原语。
    Lock Support是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结
    底, Lock Support调用的Unsafe中的native代码。

    Lock Support提供park() 和unpark() 方法实现阻塞线程和解除线程阻塞的过程
    Lock Support和每个使用它的线程都有一个许可(permit) 关联。
    每个线程都有一个相关的permit(凭证|许可证), permit最多只有一个, 重复调用unpark也不会积累凭证。

    形象的理解
    线程阻塞需要消耗凭证(permit) , 这个凭证最多只有1个。
    当调用方法时
    如果有凭证,则会直接消耗掉这个凭证然后正常退出;
    如果无凭证,就必须阻塞等待凭证可用;
    而则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。

    面试题

    为什么可以突破wait/notify的原有调用顺序?
    因为unpark获得了一个凭证, 之后再调用park方法, 就可以名正言顺的凭证消费, 故不会阻塞。
    先发放了凭证后续可以畅通无阻。

    为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
    因为凭证的数量最多为1, 连续调用两次un park和调用一次un park效果一样, 只会增加一个凭证;
    而调用两次park却需要消费两个凭证, 证不够, 不能放行。

  • 相关阅读:
    MVC第三波书店书籍登录展示页面
    虚拟机上部署K8S集群
    vulfocus——struts2-cve_2017_9791
    【C++】从文件获取json信息
    单词记忆系统三:优化音标输入(允许键盘字符直接输入和音标序号混合输入)
    访问nginx报错502日志:failed (13: Permission denied) while connecting to upstream
    GPT怎样教我用Python进行数据可视化
    人工智能与养老:技术如何助力舒适晚年
    零时科技 || Earing Farm攻击事件分析
    11 Go的作用域
  • 原文地址:https://blog.csdn.net/qq_44981526/article/details/128147335