3.LockSupport与线程中断

LockSupport与线程中断

线程中断

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

中断机制以及java相关api

如何中断一个线程

  1. 通过volatile变量实现
 static volatile boolean  isStop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!isStop) {
                System.out.println("---------t1---------");
            }
            System.out.println("---------t1 stop---------");
        }, "t1").start();

        new Thread(() -> {
            isStop = true;
        }, "t2").start();
    }
结果
---------t1---------
---------t1---------
---------t1 stop---------
  1. 使用原子变量 AtomicBoolean
    static volatile AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(() -> {
            while (!atomicBoolean.get()) {
                System.out.println("---------t1---------");
            }
            System.out.println("---------t1 stop---------");
        }, "t1").start();

        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();
    }
结果:
---------t1---------
---------t1 stop---------
  1. 使用Thread自带中断方法
 Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("---------t1 stop---------");
                    break;
                }
                System.out.println("---------t1---------");
            }
        }, "t1");
        t1.start();
        new Thread(t1::interrupt, "t2").start();
		// 或者t1自己设置标志位    
		// t1.interrupt();
执行结果
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1 stop---------

中断三大方法说明:

public void interrupt()

​ 实例方法,Just to set the interrupt flag
​ 实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程

​ 具体来说,当对一个线程,调用interrupt()时:

​ 1、如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已:被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。

​ 2、如果线程处于被阻塞状态(例如处于sleep, wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

这么使用可能会导致程序死循环

        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("---------t1 stop---------");
                    break;
                }

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("---------t1---------");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(t1::interrupt, "t2").start();

执行流程:
1、正常执行打印 ---------t1---------   中断标志位  = false
2、t2 设置中断标志位   中断标志位 = true
3、t1遇到  Thread.sleep(200); 方法  抛出 InterruptedException 并且设置清除中断标志位 中断标志位 = false
4、最终导致程序无限循环
解决:  在sleep() catch 代码块中 重新设置中断标志位 为 true

public static boolean interrupted()
​ 静态方法,Thread.interrupted();判断线程是否被中断并清除当前中断状态。

​ 这个方法做了两件事:
1. 返回当前线程的中断状态,测试当前线程是否已被中断
2. 将当前线程的中断状态清零并重新设为false,清除线程的中断状态
​ 此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次 的结果可能不一样

public boolean isInterrupted()
​ 实例方法
​ 判断当前线程是否被中断〔通过检查中断标志位)

LockSupport

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

LockSupport中的park()和 unpark()的作用分别是阻塞线程解除阻塞线程

三种线程等待和幻唤醒方法

  1. 使用object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程方式
// 1、wait()方法和  notify()方法 只能用在 synchronized 同步代码块中
       // object 的线程等待和唤醒方法
        Object o = new Object();
        new Thread(() -> {

            System.out.println("++++++++++t1开始++++++++++");
            try {
                o.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        
        }, "t1").start();

        new Thread(() -> {

            o.notify();
            System.out.println("+++++++++++t2开启唤醒+++++++");

        }, "t2").start();
结果:
++++++++++t1开始++++++++++
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at LockSupportDemo.lambda$main$0(LockSupportDemo.java:32)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at LockSupportDemo.lambda$main$1(LockSupportDemo.java:41)
	at java.lang.Thread.run(Thread.java:748)

// 2、notify在wait方法前面先执行,程序无法唤醒
        // object 的线程等待和唤醒方法
        Object o = new Object();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            synchronized (o) {
                System.out.println("++++++++++t1开始++++++++++");
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("+++++++++++t1 被唤醒+++++++++++");
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (o) {
                o.notify();
                System.out.println("+++++++++++t2开启唤醒+++++++");
            }
        }, "t2").start();
结果:
+++++++++++t2开启唤醒+++++++
++++++++++t1开始++++++++++
    
(程序没有正常退出)


  1. 使用Juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程方式
// condition类的await  signal方法
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            System.out.println("++++++++++t1开始++++++++++");
            try {
                condition.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            System.out.println("+++++++++++t1 被唤醒+++++++++++");
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            condition.signal();
            System.out.println("+++++++++++t2开启唤醒+++++++");
            lock.unlock();
        }, "t2").start();
结果:
	++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    +++++++++++t1 被唤醒+++++++++++
    
 await signal 会出现与wait notify相同的问题
  1. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

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

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),但与Semaphore不同的是,许可的累加上限是1。

    ​ park()阻塞 unpark() 唤醒

    park 源码
      public static void park() {
            UNSAFE.park(false, 0L);
        }
      public native void park(boolean var1, long var2);
    // boolean var1  =》 boolean isAbsolute()  是否拥有许可证 默认false =》 没有通行证
    // long var2 =》 long time 持续时间  默认 0l =》 不放行
    
    // permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程的发放permit,park方法才会被唤醒。
    
    unpark()  源码
        public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }
        // 要发许可证的线程  Thread thread
    
    // 正常执行
            Thread t1 = new Thread(() -> {
                System.out.println("++++++++++t1开始++++++++++");
                LockSupport.park();
                System.out.println("+++++++++++t1 被唤醒+++++++++++");
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println("+++++++++++t2开启唤醒+++++++");
            }, "t2").start();
    结果
    ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    +++++++++++t1 被唤醒+++++++++++
        
    // 先执行 unpark  再执行park
              Thread t1 = new Thread(() -> {
    
                System.out.println("++++++++++t1开始++++++++++");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                LockSupport.park();
                System.out.println("+++++++++++t1 被唤醒+++++++++++");
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                LockSupport.unpark(t1);
                System.out.println("+++++++++++t2开启唤醒+++++++");
            }, "t2").start();
    
     结果
     ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    +++++++++++t1 被唤醒+++++++++++
         
     程序完整执行----》先发许可证而后 t1线程持证上岗,不会阻塞
     
     // 发多次通行证
       Thread t1 = new Thread(() -> {
    
                System.out.println("++++++++++t1开始++++++++++");
                LockSupport.park();
                LockSupport.park();
                System.out.println("+++++++++++t1 被唤醒+++++++++++");
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                // 多次发许可
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                System.out.println("+++++++++++t2开启唤醒+++++++");
            }, "t2").start();
    // 运行结果
    ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
        
    (程序没有停止运行)
        
    

    总结
    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
    LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程

    ​ LockSupport和每个使用它的线程都有一个许可(permit)关联。
    ​ 每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
    ​ 也就是说
    ​ 线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时
    ​ 如果有凭证,则会直接消耗掉这个凭证然后正常退出;*如果无凭证,就必须阻塞等待凭证可用;
    ​ 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。

posted @ 2023-06-26 15:28  奶油炒白菜  阅读(11)  评论(0编辑  收藏  举报