线程-常用方法

基础方法

暂停线程

Sleep 方法

  • 作用:[让正在运行的线程进入睡眠状态,虚拟机/处理器在此期间不会调度该线程]{.red}

  • 使用:

    • [JDK 1.0 默认提供睡眠方法:默认时间单位是毫秒(ms)]{.blue}

      • 核心方法:Thread.sleep()

      • 代码演示

        @Slf4j(topic = "c.ThreadSleepAndYield")
        public class ThreadSleep
        {
        public static void main(String[] args) throws InterruptedException
        {
        Thread t1 = new Thread(() ->{
        try{
        log.debug("thread sleep...");
        Thread.sleep(1000);
        }
        catch (InterruptedException e){
        e.printStackTrace();
        }
        }, "t1");
        // 线程开始执行
        t1.start();
        // 状态转换: 线程从 Runnable -> Timed_Waiting
        log.debug("T1 state {}", t1.getState());
        Thread.sleep(500);
        log.debug("T1 state {}", t1.getState());
        // 主线程中断线程
        log.debug("开始执行中断 {}", System.currentTimeMillis());
        t1.interrupt();
        // 状态转换: 线程从 Runnable -> Timed_Waiting
        log.debug("T1 state {}", t1.getState());
        Thread.sleep(500);
        log.debug("T1 state {}", t1.getState());
        }
        }
    • [JDK 1.5 提供的新的睡眠方法:单位可以根据需要使用]{.blue}

      • 核心方法:Time.Unit.SECONDS.sleep()

      • 代码演示

        @Slf4j(topic = "c.ThreadSleepAndYield")
        public class ThreadSleep
        {
        public static void main(String[] args) throws InterruptedException
        {
        // 睡眠 1000 秒
        TimeUnit.SECONDS.sleep(1000);
        // 睡眠 1000 微秒
        TimeUnit.MICROSECONDS.sleep(1000);
        // 睡眠 1000 毫秒
        TimeUnit.MILLISECONDS.sleep(1000);
        // 睡眠 1000 纳秒
        TimeUnit.NANOSECONDS.sleep(1000);
        // 睡眠 1000 分钟
        TimeUnit.MINUTES.sleep(1000);
        // 睡眠 1000 小时
        TimeUnit.HOURS.sleep(1000);
        // 睡眠 1000 天
        TimeUnit.DAYS.sleep(1000);
        }
        }
  • 特点:

    • [Sleep 方法会让线程主动交出处理器的使用权,也就是放弃自己的时间片]{.red}
    • [Sleep 方法不会释放锁的所有权,也就是持有锁的线程睡眠时,别的线程依旧无法获取锁]{.red}
    • [Sleep 方法会让线程从 RUNNABLE 转为 TIMED_WAITING 状态]{.red}
    • [正在休眠的线程是可以被中断的,线程被中断之后会默认抛出异常,并且将中断标志重新置为 false]{.pink}
    • 休眠结束的线程需要重新和其他线程竞争处理器的使用权

中断方法和锁相关内容之后会详细说明

Yield 方法

  • 作用:[提示正在运行的线程主动放弃处理器的使用权,从而让其他线程执行]{.red}

  • 使用:(这个方法的效果不是很明显,不过也有办法测试)

    • 测试方式

      • 第一条线程不做任何礼让,持续执行增加数量的操作;第二条线程会边进行礼让边执行增加数量的操作
      • 观察到的结果应该是第一条线程输出语句更多,第二条线程输出语句更少,因为进行了礼让

      :::warning

      ① 线程上下文切换和线程礼让都会导致运行的线程发生改变

      ② 所以只用一次 Thread.Yield 是没有办法判断究竟是线程切换还是线程礼让造成的

      :::

    • 测试代码

      @Slf4j(topic = "c.ThreadYield")
      public class ThreadYield
      {
      public static void main(String[] args) throws InterruptedException
      {
      new Thread(()->{
      int count = 0;
      while (true)
      {
      log.debug("t1---> {}", ++count);
      }
      }, "t1").start();

      new Thread(()->{
      int count = 0;
      while (true)
      {
      // 礼让其他线程
      Thread.yield();
      log.debug(" t2---> {}", ++count);
      }
      }, "t2").start();
      }
      }
  • 特点:

    • [正在运行的线程可能放弃也可能拒绝放弃自己对于处理器的使用权,完全取决于线程自己]{.red}
      • Yield 方法的效果和线程优先级的效果类似,都是没有办法强制控制线程的
    • [Yield 方法会让线程从 RUNNABLE(running)转换为 RUNNABLE(ready)状态]{.red}
    • [Yield 方法也不会释放锁的所有权]{.red}

Join 方法

  • 作用:[调用线程阻塞式等待正在运行的线程结束]{.red}

  • 使用:

    • 核心方法:thread.join()(这里的 thread 是等待的线程对象而不是调用线程或者 Thread 类)

    • [无限期阻塞等待:调用线程会等待线程运行结束才开始运行]{.pink}

      @Slf4j(topic = "c.ThreadJoin")
      public class ThreadJoin
      {
      public static void main(String[] args) throws InterruptedException
      {
      Thread t1 = new Thread(() ->
      {
      log.debug("{} 开始执行...", Thread.currentThread().getName());
      MySleep.sleep(1);
      log.debug("{} 结束执行...", Thread.currentThread().getName());
      }, "t1");
      // 线程开始执行
      t1.start();
      // 主线程(调用线程)等待 Thread-1 线程结束
      t1.join();
      // 主线程开始执行
      log.debug("主线程执行...")
      }
      }
    • 有限期阻塞等待

      • [如果等待的线程提前结束,那么调用线程也会提前结束]{.red}

      • [如果调用线程已经等待了设定的时间,等待的线程依然没有结束,那么调用线程就会不再等待]{.red}

        @Slf4j(topic = "c.ThreadJoin")
        public class ThreadJoin
        {
        public static void main(String[] args) throws InterruptedException
        {
        Thread t1 = new Thread(() ->
        {
        try{
        // 线程休眠 2 秒
        TimeUnit.SECONDS.sleep(2);
        }
        catch (InterruptedException e){
        e.printStackTrace();
        }
        }, "t1");

        long start = System.currentTimeMillis();
        // 线程开始启动
        t1.start();
        // 主线程等待线程结束
        // 如果线程超过 4 秒还没结束, 就不再等待;如果少于 4 秒, 主线程也会提前结束等待
        t1.join(4000);
        log.debug("cost = {}", System.currentTimeMillis() - start);
        }
        }
  • 特点:

    • [Join 方法的本质就是 Wait 方法]{.red}

      public final synchronized void join(long millis) throws InterruptedException {
      long base = System.currentTimeMillis();
      long now = 0;

      if (millis < 0) {
      throw new IllegalArgumentException("timeout value is negative");
      }
      // 不带有时延的等待
      if (millis == 0) {
      // 判断调用线程是否存活
      while (isAlive()) {
      // 如果调用线程存活, 就会调用 wait 方法进行等待
      wait(0);
      }
      } else {
      // 这里是带有时延的等待
      }
      }
    • [调用线程 从 RUNNABLE 转换为 WAITING 状态]{.red}

    • [调用线程到的阻塞等待过程是可以被其余线程中断的]{.red}

    • [调用线程阻塞等待多个线程时,等待的时间取决执行时间最长的那个线程]{.pink}

      1. 调用线程等待 Thread-1、Thread-2 两个线程,Thread-1 执行 1秒,Thread-2 执行 2秒
      2. 调用线程阻塞式等待这两个线程,那么调用线程最终只会等待 2 秒,而不是 3 秒
      3. 多核处理的情况下线程基本都是并行,所以显然只需要等待 2 秒
      @Slf4j(topic = "c.ThreadJoin")
      public class ThreadJoin
      {
      private static int count = 0;
      private static int number = 0;
      // 节省代码篇幅, 不再重复写相同的代码
      public static void main(String[] args) throws InterruptedException
      {
      Thread t1 = new Thread(() ->{
      // 线程休眠 1 秒
      }, "t1");

      Thread t2 = new Thread(() ->{
      // 线程休眠 2 秒
      }, "t2");
      // 记录开始时间
      long start = System.currentTimeMillis();
      // 启动两个线程
      t1.start();
      t2.start();
      // 等待两个线程结束
      t1.join(4000);
      t2.join(4000);
      // 打印主线程等待的时间
      log.debug("count = {}, number = {}, cost = {}", count, number,
      System.currentTimeMillis() - start);
      }
      }

Park & Unpark 方法

中断线程

Interrupt 方法

  • 作用:[设置需要中断的线程的中断标志位 true,线程根据中断标志 自行决定 之后的行为]{.red}

  • 使用:

    • 核心方法:thread.interrupt()

    • 代码演示

      @Slf4j(topic = "c.ThreadInterrupt")
      public class ThreadInterrupt
      {
      public static void main(String[] args) throws InterruptedException
      {
      Thread t1 = new Thread(() ->
      {
      log.debug("{} 开始休眠...", Thread.currentThread().getName());
      try{
      // 休眠 5 秒
      TimeUnit.SECONDS.sleep(5);
      }
      catch (InterruptedException e){
      // 默认会抛出异常
      e.printStackTrace();
      }
      }, "t1");
      // 启动线程
      t1.start();
      // 主线程休眠 1 秒, 确保在子线程启动之后再进行打断
      TimeUnit.SECONDS.sleep(1);
      log.debug("主线程准备打断线程...");
      // 打断线程的休眠
      t1.interrupt();
      // 查看线程被中断之后的状态
      // 注意:sleep 被中断后中断标志位会被重新置为 false, 所以你看到的结果是 false 而不是true
      log.debug("线程状态的打断标记 {}", t1.isInterrupted());
      }
      }
  • 特点:

    • 中断标志默认设置为 false
    • [Interrupt 方法可以中断 Sleep、Join、Wait 方法造成的等待过程,默认将会抛出异常]{.red}
    • [Interrupt 方法配合 IsInterrupted \ Interrupted 方法可以实现优雅地停止线程]{.red}
    • [Sleep、Join、Wait 方法在被中断之后默认将中断标志重新设置为 false]{.pink}

IsInterrupted 方法

  • 作用:[获取线程的中断标志位的状态]{.red}

  • 细节:

    • [IsInterrupted 方法 默认 不会清除中断标志位,但是可以设置清除中断标志位]{.pink}

      // 传入为真的布尔变量就可以清除中断标志位了
      private native boolean isInterrupted(boolean ClearInterrupted);
    • [IsInterrupted 方法获取的 调用该方法的线程对象 的中断标志位状态,而不是调用线程的]{.red}

  • 使用

    • 核心方法:thread.isInterrupted()(实例方法)

    • 代码演示:[Interrupt + IsInterrupted 实现优雅的停止线程]{.red}

      @Slf4j(topic = "c.ThreadInterrupt")
      public class ThreadInterrupt
      {
      public static void main(String[] args) throws InterruptedException
      {
      Thread t2 = new Thread(()->{
      // 子线程持续运行, 每次都判断自己是否被中断
      while (true){
      // 利用中断标记决定线程是继续运行还是结束
      // Thread.currentThread() 获取当前运行的线程对象
      if (Thread.currentThread().isInterrupted()){
      log.debug("线程结束...");
      break;
      }
      }
      }, "t2");
      // 线程开始启动
      t2.start();
      // 主线程休眠 1 秒
      TimeUnit.SECONDS.sleep(1);
      log.debug("主线程准备打断线程...");
      // 打断线程的休眠
      t2.interrupt();
      }
      }

Interrupted 方法

  • 作用:[获取线程的中断标志位状态,并且重新将中断标志位置为 false]{.red}

  • 细节:

    • [返回的结果是被 重置之前 的结果,而不是重置之后的结果]{.pink}

    • [IsInterrupted 方法获取的是 调用线程 的状态,而不是其他线程对象的状态]{.red}

      public static boolean interrupted() {
      // 返回的是当前运行的线程的中断标志位, 内部调用的就是 isInterrupted 方法
      return currentThread().isInterrupted(true);
      }
  • 使用:

    • 核心方法:Thread.isInterrupted()(静态方法)

    • 代码演示:[区别两种获取中断标志位状态的方法]{.aqua}

      @Slf4j(topic = "c.ThreadInterrupt")
      public class ThreadInterrupt
      {
      public static void main(String[] args) throws InterruptedException
      {
      Thread thread = new Thread(() -> {
      while (true) {
      // 线程持续运行
      }
      });
      // 启动线程
      thread.start();
      // 设置线程的中断标志位
      thread.interrupt();
      // 获取中断标志位
      log.debug("{}", thread.isInterrupted());
      // 这里获取的依然是主线程的中断标志位
      // 因为 interrupted 是静态方法, 所以使用对象调用或者类调用是相同的
      log.debug("{}", thread.interrupted());
      // 这里获取的就是主线程的中断标志位, 而不是子线程的
      log.debug("{}", Thread.interrupted());
      // 再次获取中断标志位, 上面两个方法都没有清除子线程的中断标志位, 所以依然是 true
      log.debug("{}", thread.isInterrupted());
      }
      }

废弃方法

Stop 方法

Suspend 方法

Resume 方法

Author: Fuyusakaiori
Link: http://example.com/2021/10/19/juc/线程基础/基础方法/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.