LockSupport与线程中断

线程中断机制

如何终止一个线程

void interrupt()

static boolean interrupt()

boolean isInterrupted()

什么是线程中断

首先

一个线程不应该有其他线程来强制中断或停止,而是应该有线程自己自行停止,自己来决定自己的命运。

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

其次

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

因此java提供了一种用于停止线程的协商机制–中断,也即中断标识协商机制

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

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

按照你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要你自己写代码实现

每个线程对象都有一个中断标识位,用于表示线程是否被中断;该标识位为ture表示中断,为false表示为中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用

中断相关API

  • void interrupt()

​ 实例方法,Just to set the interrupt flag

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

  • static boolean interrupt()

​ 判断线程是否被中断并清除当前中断状态

​ 这个方法做了两件事:

	1. 返回当前线程的中断状态,测试当前线程是否已被中断
	1. 将当前线程的中断状态清零并重新设置为false,清除线程的中断状态

​ 如果连续两次调用此方法,则第二次调用将返回false,因为连续两次的结果可能不一样

  • boolean isInterrupted()

​ 判断当前线程是否被中断(通过检查中断标志位)

面试题

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

    • volatile变量实现

      public class ThreadTest {
          static volatile boolean isStop = false;// volatile 保证可见性
          public static void main(String[] args) {
              new Thread(() -> {
                  while (true) {
                      if (isStop) {
                          System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止");
                          break;
                      }
                      System.out.println("t1 ---- hello world");
                  }
              }, "t1").start();
      
              try {
                  TimeUnit.MILLISECONDS.sleep(20);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
      
              new Thread(() -> {
                  isStop = true;
              }, "t2").start();
          }
      
      }
      
    • AtomicBoolean实现

      public class ThreadTest {
          static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
      
          public static void main(String[] args) {
              new Thread(() -> {
                  while (true) {
                      if (atomicBoolean.get()) {
                          System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止");
                          break;
                      }
                      System.out.println("t1 ---- hello world");
                  }
              }, "t1").start();
      
              try {
                  TimeUnit.MILLISECONDS.sleep(20);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
      
              new Thread(() -> {
                  atomicBoolean.set(true);
              }, "t2").start();
          }
      
      }
      
    • 通过Thread类自带的中断api实例方法实现

      public class ThreadTest {
          public static void main(String[] args) {
              Thread t1 = new Thread(() -> {
                  while (true) {
                      if (Thread.currentThread().isInterrupted()) {
                          System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止");
                          break;
                      }
                      System.out.println("t1 ---- hello world");
                  }
              }, "t1");
              t1.start();
      
              try {
                  TimeUnit.MILLISECONDS.sleep(20);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
      
              new Thread(() -> {
                  t1.interrupt();
                  System.out.println("发出协商");
              }, "t2").start();
          }
      
      }
      

      总结:

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

      • 如果线程处于正常活动状体,那么该线程的中断标志设置为true,仅此而起

        被设置中断标志位的线程将继续正常运行,不受影响

        所以,interrupt()并不能真正中断线程,需要被调用的线程自己进行配合才行

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

      • 中断不活动的线程不受任何影响

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

    public static void main(String[] args) {
         // 实例方法interrupt()仅仅时设置线程的中断状态位置设置为true,不会停止线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("----" + i);
            }
            System.out.println("t1线程调用interrupt()后的中断标志 02" + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        System.out.println("t1线程默认的中断标志 : "+t1.isInterrupted());
    
        // 暂停毫秒
        try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}
        t1.interrupt();
        System.out.println("t1线程 interrupt()后的中断标志 : "+t1.isInterrupted());
    
        try {TimeUnit.MILLISECONDS.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
        System.out.println("t1线程 2000 interrupt()后的中断标志 : "+t1.isInterrupted());
    }
    
     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 {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
    //                    Thread.currentThread().interrupt();
                        throw new RuntimeException(e);
                    }
                    System.out.println("---- hello ");
                }
            }, "t1");
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
            new Thread(() -> {
                t1.interrupt();
            },"t2").start();
    
        }
    

请添加图片描述

sleep()方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过调用th.interrupt()方法在此将中断标识置为true,这就是导致无限循环的原因

  1. 静态方法Thread.interrupt(),谈谈理解

​ 返回之前状态并重置为false

LockSupport

是什么

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

park():除非许可证可用,否则禁用当前线程以进行线程调度

unpark(Thread thread):如果给定线程尚不可用,则为其提供许可证

线程等待唤醒机制

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

    缺陷

    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) {
                    throw new RuntimeException(e);
                }
    
                System.out.println(Thread.currentThread().getName() + "\t --- 被唤醒");
            }
        }, "t1").start();
    
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    
    
        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t --- 发出通知");
            }
        }, "t2").start();
    }
    

    wait和notify方法必须要在同步代码块或者方法里面,且成对使用

    先wait后notify才ok

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

    缺陷

     public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(() -> {
    //            lock.lock();
                try {
                    System.out.println("-- come in");
                    condition.await();
                    System.out.println("被唤醒");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
    //                lock.unlock();
                }
            }).start();
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            new Thread(() -> {
    //            lock.lock();
                try {
                    System.out.println("发出通知");
                    condition.signal();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
    //                lock.unlock();
                }
            }).start();
    
        }
    

    Condition中的线程等待和唤醒方法,需要先获取锁

    一定要先await后signal,不要反了

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

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("-- come in");
        LockSupport.park();
        System.out.println("被唤醒");
    }, "t1");
    t1.start();

    try {
        TimeUnit.MILLISECONDS.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    new Thread(() -> {
        System.out.println("-- 发通知");
        LockSupport.unpark(t1);
    },"t2").start();

}

解决:

可以先唤醒后等待

无锁

许可证最多只有一个,注意成对出现

重点说明

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

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程

LockSupport和每个使用它的线程都有一个许可(permit)关联

每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证

形象的理解:

线程阻塞需要消耗凭证(permit),这个凭证组多只有一个

当调用哦park方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出
  • 如果无凭证,就必须阻塞等待凭证可用

而unpark则相反,他会增加一个凭证,但凭证最多只能1个,累加无效

面试题

  1. 为什么可以突破wait/notify的原有调用顺序?

    因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

    先发放了凭证后续可以畅通无阻

  2. 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

​ 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消耗两个凭证,证不够,不能放行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值