栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

juc-02-interrupt()、join()、yield()和守护线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

juc-02-interrupt()、join()、yield()和守护线程

这篇文章,给大家介绍一下 Thread 类的进阶知识,介绍并演示 interrupt()、join()、yield()的作用,还有守护线程的特点。

1 Thread 常用API
方法 描述
void start() 启动此线程,JVM将会执行这个线程的 run() 方法
void interrupt() 将线程置为中断状态,中断标识设置为true。
boolean isInterrupted() 判断此线程是否已处于中断状态,此方法不影响线程的中断状态。
static boolean interrupted() 静态方法,判断此线程是否已处于中断状态,并清空此线程的中断状态,也就是说,如果此线程已经被中断了,调用此方法会返回true,并将此线程设置为非中断状态,中断标识设置为false。
void join() 阻塞等待此线程死亡(运行结束),
举例:如果在主线程中执行了子线程(thread)thread.join(),那主线程会阻塞等待直到子线程thread死亡。
看不懂描述,可以下面的demo。
void join(long millis) 最多阻塞等待多少毫秒等待此线程死亡(运行结束),0表示永远等待直到此线程死亡
void sleep(long millis) 使当前执行的线程在指定的毫秒数内休眠(暂时停止执行)
void yield() 让出cpu的执行权,将线程从运行转到就绪状态,但是下个时间片,该线程依然有可能被再次选中运行。
void setDaemon(boolean on) 入参为true时,设置当前线程为守护线程,否则为用户线程,此方法在start()方法之前调用才有效。
守护线程:与父线程同时死亡(结束)。
用户线程:用户线程是独立存在的,不会因为其他用户线程退出而退出
boolean isDaemon() 判断当前线程是否是守护线程
Thread中几个方法的作用

2 如何安全停止线程 2.1 线程自然死亡(结束)
  • 自然执行完
  • 抛出未处理异常
2.2 stop(),destroy(),resume(),suspend()已过时,不建议使用
  • stop():强制结束线程,会导致线程不会正确释放资源,
  • suspend():挂起,线程不会释放资源,容易导致死锁。
2.3 推荐使用 interrupt(),安全停止线程

注意: java线程是 协作式,而非抢占式
合理方式: 应该调用一个线程的 interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。

一般情况是: 我们在当前线程的run()方法中,判断当前线程的中断状态是否为true,进而决定是否退出当前线程的运行。

3 InterruptedException

Thread的sleep()、join()和Object的wait()方法,都能够抛出InterruptedException。
interrupt()方法只是将线程的中断标志位置为true,sleep()、join() 、wait()会不断检查线程的中断状态,如果当前线程中断标识位为ture,则会抛出InterruptEdException。

注意: 线程抛出InterruptEdException后,清空此线程的中断状态,也就是说,将此线程设置为非中断状态,中断标识设置为false。
抛出的InterruptEdException被catch后,再调用isInterrupted(),会返回false。

4 interrupt()、isInterrupted()、interrupted() 的使用
  • interrupt():只是将线程的中断标志位置为true,并不是强行关闭这个线程,线程是否中断,由线程本身决定。
  • isInterrupted():判断此线程是否已处于中断状态,此方法不影响线程的中断状态。
  • interrupted():静态方法,获取当前线程的中断状态,不管当前线程中断状态如何,接着清空当前线程的中断状态。
4.1 演示 interrupt() 和 isInterrupted() 安全地退出线程运行

    @Test
    public void testInterrupt() throws InterruptedException {
 // 演示 interrupt() 、 isInterrupted() 和 InterruptedException
 Thread thread = new Thread(new Runnable() {
     @Override
     public void run() {
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  System.out.println("子线程开始执行任务..." + formatter.format(LocalDateTime.now()));
  // 如果当前线程不处于中断状态,则每秒打印当前时间
  while (!Thread.currentThread().isInterrupted()) {
      try {
   // sleep() 方法会检测线程的中断状态,如果已中断,则抛出InterruptedException
   TimeUnit.SECONDS.sleep(1);
   System.out.println("打印当前时间:" + formatter.format(LocalDateTime.now()));
      } catch (InterruptedException e) {
   System.out.println("catch InterruptedException:" + e.getMessage());
   // InterruptedException 会清空中断状态,所以此时的中断状态为 false
   System.out.println("当前线程中断状态:" + Thread.currentThread().isInterrupted());
//   e.printStackTrace();
   // InterruptedException 会清空中断状态,需要再次执行interrupt(),才能将线程状态置为已中断,退出循环
   Thread.currentThread().interrupt();
      }
  }
  System.out.println("子线程任务结束..." + formatter.format(LocalDateTime.now()));
     }
 });
 thread.start();
 TimeUnit.SECONDS.sleep(3);
 System.out.println("主线程执行子线程的interrupt()");
 // 只是将线程的中断标志位置为true,并不是强行关闭这个线程,线程是否中断,由线程本身决定。
 thread.interrupt();
    }

运行结果:

子线程开始执行任务...2020-09-17 18:18:44
打印当前时间:2020-09-17 18:18:45
打印当前时间:2020-09-17 18:18:46
主线程执行子线程的interrupt()
catch InterruptedException:sleep interrupted
当前线程中断状态:false
子线程任务结束...2020-09-17 18:18:47
4.2 演示 interrupted()
    @Test
    public void testInterrupted() throws InterruptedException {
 // 演示 interrupted()
 Thread thread = new Thread(new Runnable() {
     @Override
     public void run() {
  // 静态方法 Thread.interrupted() 获取当前线程的中断状态,不管当前线程中断状态如何,接着清空当前线程的中断状态
  System.out.println("静态方法 Thread.interrupted() 获取当前线程的中断状态:" + Thread.interrupted() + ",然后清空中断状态");
  System.out.println("成员方法 interrupted() 获取当前线程的中断状态:"+Thread.currentThread().interrupted());
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  System.out.println("子线程开始执行任务..." + formatter.format(LocalDateTime.now()));
  // 如果当前线程不处于中断状态,则每秒打印当前时间
  if (!Thread.currentThread().isInterrupted()) {
      System.out.println("打印当前时间:" + formatter.format(LocalDateTime.now()));
  }
  System.out.println("子线程任务结束..." + formatter.format(LocalDateTime.now()));
     }
 });
 thread.start();
 thread.interrupt();
 TimeUnit.SECONDS.sleep(3);
    }

运行结果:

静态方法 Thread.interrupted() 获取当前线程的中断状态:true,然后清空中断状态
成员方法 interrupted() 获取当前线程的中断状态:false
子线程开始执行任务...2020-09-17 18:20:13
打印当前时间:2020-09-17 18:20:13
子线程任务结束...2020-09-17 18:20:13
5 join()、join(long millis) 的使用

join():阻塞等待此线程死亡(运行结束)。
join(long millis):设置等待的超时时间,阻塞等待此线程死亡(运行结束),若超时时间为0,则一直等待直到此线程死亡。
应用场景:这个经常用在线程间的协作,一个线程等另一个线程运行结束后,再继续运行。

线程自然死亡(结束)

  • 自然执行完
  • 抛出未处理异常

看不懂就看看代码,应该容易理解一点:

5.1 join() demo 演示一个线程等另一个线程运行结束后再继续运行
public class TestJoin {

    @Test
    public void testJoin() throws InterruptedException {
 // 演示 join() 方法使用
 //线程自然死亡(结束):自然执行完或者抛出未处理异常

 // main线程开始运行
 System.out.println(Thread.currentThread().getName() + "开始运行...");
 // t1 不需要等待其他线程运行结束
 JoinThread t1 = new JoinThread("t1", null);
 // t2 线程等待 t1 线程死亡,再继续往下运行
 JoinThread t2 = new JoinThread("t2", t1);
 // t3 线程等待 t2 线程死亡,再继续往下运行
 JoinThread t3 = new JoinThread("t3", t2);
 // t4 线程等待 t3 线程死亡,再继续往下运行
 JoinThread t4 = new JoinThread("t4", t3);

 t1.start();
 t2.start();
 t3.start();
 t4.start();
 // main线程等待 t4 线程死亡,再继续往下运行
 t4.join();
 System.out.println(Thread.currentThread().getName() + "继续运行");
 System.out.println(Thread.currentThread().getName() + "运行结束...");
    }
}

//自定义线程类
class JoinThread extends Thread {

    // previousThread:前一个线程
    // 当前线程等待 previousThread 运行结束再继续运行
    private Thread previousThread;

    public JoinThread(String name, Thread previousThread) {
 super(name);
 this.previousThread = previousThread;
    }

    @Override
    public void run() {
 if (this.previousThread != null) {
     System.out.println(Thread.currentThread().getName() + "调用" + this.previousThread.getName() + ".join(),等待" + this.previousThread.getName() + "死亡");
     try {
  // 当前线程等待 previousThread 运行结束再继续运行
  this.previousThread.join();
     } catch (InterruptedException e) {
  e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName() + "继续运行");
 }
 try {
     // sleep 2 秒,模拟业务执行花了2秒
     TimeUnit.SECONDS.sleep(2);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName() + "运行结束...");
    }
}

运行结果:

main开始运行...
t2调用t1.join(),等待t1死亡
t4调用t3.join(),等待t3死亡
t3调用t2.join(),等待t2死亡
t1运行结束...
t2继续运行
t2运行结束...
t3继续运行
t3运行结束...
t4继续运行
t4运行结束...
main继续运行
main运行结束...
5.2 join(long millis) demo

    @Test
    public void testJoinWithTimeOut() throws InterruptedException {
 // 演示 join(long millis) 方法使用: 设置等待的超时时间,阻塞等待此线程死亡(运行结束),若超时时间为0,则一直等待直到此线程死亡。

 // main线程开始运行
 System.out.println(Thread.currentThread().getName() + "开始运行...");
 Thread thread = new Thread(()->{
     System.out.println(Thread.currentThread().getName() + "开始运行...");
     try {
  // sleep 5 秒,模拟业务执行花了5秒
  TimeUnit.SECONDS.sleep(5);
     } catch (InterruptedException e) {
  e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName() + "结束...");

 });
 thread.start();
 // join(),阻塞等待 thread 运行结束,设置超时时间为 3000 ms
 thread.join(3000);
 System.out.println(Thread.currentThread().getName() + "继续运行");
 System.out.println(Thread.currentThread().getName() + "运行结束...");
 TimeUnit.SECONDS.sleep(3);
    }

运行结果:

main开始运行...
Thread-0开始运行...
main继续运行
main运行结束...
Thread-0结束...
6 yield() 的使用

yield():当前线程让出cpu的执行权,将线程从运行转到就绪状态,但是下个时间片,该线程依然有可能被再次选中运行。
很多情况下,看不出它让出CPU的效果,因为下个时间片它依然有机会抢到cpu资源

public class TestYield {

    @Test
    public void testYield() throws InterruptedException {
 // 演示 yield() 方法使用
 Thread t1 = new Thread(new Runnable() {
     @Override
     public void run() {
  for (int i = 0; i < 5; i++) {
      if (i == 2) {
   System.out.println(Thread.currentThread().getName() + "--> " + i + " -- yield");
   // yield():线程让出CPU,变成就绪状态
   Thread.currentThread().yield();
      }else
   System.out.println(Thread.currentThread().getName() + "--> " + i);
  }
     }
 }, "t1");
 Thread t2 = new Thread(new Runnable() {
     @Override
     public void run() {
  for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + "=== " + i);
  }
     }
 }, "t2");
 t1.start();
 t2.start();
 TimeUnit.MILLISECONDS.sleep(1500);
    }
}

运行结果:

t1--> 0
t1--> 1
t1--> 2 -- yield
t2=== 0
t2=== 1
t2=== 2
t2=== 3
t2=== 4
t1--> 3
t1--> 4
7 setDaemon() 守护线程

守护线程:与父线程同时死亡(结束),也就是说,当父线程运行结束或者抛出异常导致死亡时,不管守护线程业务逻辑有没有执行结束,守护线程也会同时死亡。
用户线程:用户线程是独立存在的,不会因为其他用户线程退出而退出

注意: 守护线程就算有finally代码块,也不能保证一定执行。

7.1 演示守护线程的finally代码块不能保证一定执行
public class TestDaemon {

    @Test
    public void testDaemon() throws InterruptedException {
 // 演示 setDaemon()、isDaemon() 方法使用
 Thread t1 = new Thread(new Runnable() {
     @Override
     public void run() {
  try {
      //判断当前线程是不是守护线程
      System.out.println(Thread.currentThread().getName() + "是守护线程: " + Thread.currentThread().isDaemon());
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      // 如果当前线程不处于中断状态,则每秒打印当前时间
      while (!Thread.currentThread().isInterrupted()) {
   try {
// sleep() 方法会检测线程的中断状态,如果已中断,则抛出InterruptedException
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " 打印当前时间:" + formatter.format(LocalDateTime.now()));
   } catch (InterruptedException e) {
e.printStackTrace();   
// InterruptedException 会清空中断状态,需要再次执行interrupt(),才能将线程状态置为已中断,退出循环
Thread.currentThread().interrupt();
   }
      }
      System.out.println(Thread.currentThread().getName() + " 任务结束..." + formatter.format(LocalDateTime.now()));
  } finally {
      System.out.println(Thread.currentThread().getName() + "执行 finlly 代码块");
  }
     }
 }, "t1");
 //设置为守护线程,在start()方法之前调用才有效
 t1.setDaemon(true);
 t1.start();
 TimeUnit.MILLISECONDS.sleep(3000);
    }
}

运行结果:

t1是守护线程: true
t1 打印当前时间:2020-09-18 11:08:52
t1 打印当前时间:2020-09-18 11:08:53

通过上面的运行结果可以看出,当父线程(主线程)死亡(运行结束时),守护线程也终止运行(死亡),异常信息栈没有打印,finally代码块也没有执行。

通过上面的测试用例,给大家演示了 interrupt()、join()、yield()的作用,还有守护线程的特点。
这几个方法也是Java面试中经常碰到的知识点,希望大家能够在工作中学以致用,正在求职的朋友也能很好的应对面试。

代码:
https://github.com/wengxingxia/002juc.git

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号