Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt

简介: Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法、线程沟通方法的灵活运用。这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一。Java线程的生命周期Java线程类定义了New、Runnable、Running Man、Blocked和Dead五种状态。

Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法、线程沟通方法的灵活运用。这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一。

Java线程的生命周期

Java线程类定义了New、Runnable、Running Man、Blocked和Dead五种状态。

New

当初始化了一个线程对象之后,线程就进入了New的状态。此时JVM会为其分配内存、初始化成员变量的值,获取当前线程为父线程。

Runnable

当调用线程对象的start方法之后,就进入Runnable状态。JVM会为其创建虚拟机栈程序计数器。此时仅表明线程可以开始运行,但何时运行取决于JVM的线程调度器。

Running

当线程获取到CPU资源的时候,就进入Running状态执行线程方法了。如果线程数大于多处理器的数目,会存在多个线程轮换,尽管多个处理器会同时并行处理几个线程。

线程调度的细节取决于底层平台,当Running的线程调用其yield方法或失去CPU资源的时候,即回到Runnable状态

Blocked

当发生如下情况,线程会被阻塞/重新进入Runnable状态:

1. 线程调用sleep等可中断方法            ===>    sleep方法经过指定时间/其它线程调用了阻塞线程的interrupt方法

2. 线程调用了一个阻塞式IO              ===>    调用的阻塞式IO方法返回 

3. 试图获取一个正被使用的同步锁           ===>    成功获取同步锁

4. 调用了wait/await方法               ===>    其它线程发出了notify/notifyAll/signal/signalAll

5. 调用了其它线程的join方法,进入阻塞         ===>     调用了join线程的线程执行体完成或死亡

6. 线程调用suspend方法(容易导致死锁,不建议使用) ===>     被调用了resume方法

Dead

当发生如下情况,线程结束

1. 线程执行体完成

2. 抛出未捕获的异常或错误

3. 直接调用stop方法(容易导致死锁,不建议使用)

可通过调用线程对象的isAlive方法,如果处于新建和死亡状态会返回false

线程管理

常用的线程管理包括设置后台线程、设置优先级、join、sleep、yield、以及interrupt

设置后台线程

setDaemon(true):设置为后台线程

isDaemon():用于判断指定线程是否为后台线程

设置优先级

setPriority(int priority):设置优先级

getPriority():获取优先级

 1 public class ThreadPriority {
 2 
 3     public static void main(String[] args) {
 4         Thread t1 = new Thread(()->
 5         {
 6             while(true) {
 7                 System.out.println("t11111");
 8             }
 9         }, "t1");
10         t1.setPriority(Thread.NORM_PRIORITY);
11         
12         Thread t2 = new Thread(()->{
13             while (true) {
14                 System.out.println("t22222");
15             }
16         });
17         t2.setPriority(10);
18 
19         t1.start();
20         t2.start();
21     }
22 
23 }
View Code

join

在某个程序执行流中(如main线程),调用其它线程的join方法,调用线程(如main线程)将被阻塞,直到被join方法加入的线程执行完为止。

join线程可以理解为把一个问题分为若干小问题由不同的线程处理,在其它线程处理完毕后,再回到运行状态的概念。

 1 public class ThreadJoin {
 2     
 3     private static void shortSleep() {
 4         try {
 5             TimeUnit.SECONDS.sleep(1);
 6         } catch (InterruptedException e) {
 7             e.printStackTrace();
 8         }
 9     }
10     
11     private static Thread create(int seq) {
12         return new Thread(() -> {
13             for (int i = 0; i < 10; i++) {
14                 System.out.println(Thread.currentThread().getName() + "#" + i);
15                 shortSleep();
16             }
17         }, String.valueOf(seq));
18     }
19 
20     public static void main(String[] args) throws InterruptedException {
21         
22         List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
23         
24         threads.forEach(Thread::start);
25         //在main线程中, 依次调用thread的join方法, main会进入阻塞, 等其它线程完成了再行继续
26         for(Thread thread : threads) {
27             thread.join();
28         }
29         
30         for(int i = 0; i < 10; i++) {
31             System.out.println(Thread.currentThread().getName() + "#" + i);
32             shortSleep();
33         }
34 
35     }
36 
37 }
View Code

sleep

Thread类的静态方法,用于暂停线程的执行。一般建议使用1.5后新增的TimeUnit类来更好的暂停线程

 1 public class ThreadSleep {
 2     
 3     private static void sleep(int ms) {
 4         try {
 5             TimeUnit.SECONDS.sleep(ms);
 6         } catch (InterruptedException e) {
 7             e.printStackTrace();
 8         }
 9     }
10 
11     public static void main(String[] args) {
12 
13         new Thread(() -> 
14         {
15             long startTime = System.currentTimeMillis();
16             sleep(2);
17             long endTime = System.currentTimeMillis();
18             System.out.println(String.format("Total spend %d second", (endTime - startTime)));
19         }).start();
20         
21         /*
22          * Thread sleep times depends on your system
23          */
24         long startTime = System.currentTimeMillis();
25         sleep(3);
26         long endTime = System.currentTimeMillis();
27         System.out.println(String.format("Main thread total spend %d second", (endTime - startTime)));
28         
29     }
30 }
View Code

interrupt

interrupt方法,主要是另外一个线程用于打断一个阻塞状态的线程,使其重新进入Runnable状态。如果一个阻塞方法(如sleep、wait)可以被interrupt至Runnable状态,这些方法又叫“可中断方法”。interrupt包括3个方法:

1. void interrupt()

2. static boolean interrupted()

3. boolean isInterrupted()

 1 public class ThreadInterrupt {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4         Thread thread = new Thread() {
 5             public void run() {
 6                 while(true) {
 7                     try {
 8                         /*
 9                          * sleep为可中断方法, 因此会擦除interrupt标志;
10                          * 如果注释掉sleep方法, thread收到中断信号后,interrupt标志不会被移除
11                          */
12                         TimeUnit.MINUTES.sleep(2);
13                     } catch (InterruptedException e) {
14                         System.out.printf("I am be interrupted? %s\n", isInterrupted());
15                     }
16                 }
17             }
18         };
19         thread.setDaemon(true);
20         thread.start();
21         //确保thread线程启动完毕
22         TimeUnit.SECONDS.sleep(2);
23         System.out.printf("I am be interrupted? %s\n", thread.isInterrupted());
24         //在main线程中, 调用线程对象thread的interrupt()方法, 中断thread线程的sleep阻塞状态.
25         thread.interrupt();
26         //确保thread线程进入Runnable并被线程调度器调动
27         TimeUnit.SECONDS.sleep(2);
28         System.out.printf("I am be interrupted? %s\n", thread.isInterrupted());
29     }
30 }

事实上,在Thread中维护了一个interrupt的flag,它对线程对象的影响在于:

1. 如果一个线程在运行过程中,另外一个线程调用interrupt方法,相当于似得interrupt的flag为true(上面的代码中,注释掉thread线程的sleep代码,可以看到28结果为true)

2. 如果一个线程因为可中断方法而导致的阻塞,另一个线程调用interrupt方法,阻塞线程捕获中断信号,会把interrupt的flag改为false,实现重置。

static boolean interrupted()方法除了判断线程是否中断外,会直接把interrupt的flag变为true。

 1 public class ThreadInterrupted {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4         
 5         Thread thread = new Thread(() -> {
 6             while (true) {
 7                 if(Thread.interrupted()) {
 8                     System.out.println("Yes my interrupt flag has been removed!");
 9                 }else {
10                     System.out.println("I am running");
11                 }
12             }
13         });
14         
15         thread.setDaemon(true);
16         thread.start();
17         
18         //Main sleep to make sure thread start
19         TimeUnit.MILLISECONDS.sleep(2);
20         thread.interrupt();
21         //以下显示false
22         System.out.println("Main thread is interrupted? " + Thread.interrupted());
23         
24         Thread.currentThread().interrupt();
25         //以下显示true
26         System.out.println("Main thread is interrupted? " + Thread.currentThread().isInterrupted());
27         
28         try {
29             TimeUnit.MINUTES.sleep(2);
30         } catch (Exception e) {
31             System.out.println("I am be interrupted!!!!!!");
32         }
33     }
34 }

yield

与sleep方法不同,yield方法只是让当前线程暂停一下,以便线程调度器操作线程调度。该方法不会让线程进入阻塞

 1 public class ThreadYield {
 2     
 3     private static Thread create(int index) {
 4         try {
 5             TimeUnit.SECONDS.sleep(1);
 6         } catch (InterruptedException e) {
 7             e.printStackTrace();
 8         }
 9         return new Thread(()->
10         {
11             if (index == 0) {
12                 Thread.yield();
13             }
14             System.out.println(index);
15         });
16     }
17 
18     public static void main(String[] args) {
19         IntStream.range(0, 2).mapToObj(ThreadYield::create).forEach(Thread::start);
20     }
21 }
View Code

线程关闭

线程关闭不建议使用stop方法,一般来说,线程关闭分为正常和异常两种情况。本文不讨论线程执行体正常完成退出以及异常抛出这两种情况,就业务逻辑中介绍两种主动关闭线程的方法。

1. 通过捕获interrupt中断信号来关闭线程

 1 public class InterruptThreadExit {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4         
 5         Thread t = new Thread() {
 6             @Override
 7             public void run() {
 8                 System.out.println("I will start to work");
 9                 //此处通过IsInterrupt或interrupted方法来判断中断状态, 以便控制while循环实现控制线程关闭的目的
10                 while(!interrupted()) {
11                     //System.out.println(isInterrupted());
12                 }
13                 System.out.println(isInterrupted());
14                 System.out.println("I will exit");
15             }
16         };
17         t.start();
18         TimeUnit.SECONDS.sleep(3);
19         System.out.println("ready to send interrupt signal");
20         t.interrupt();
21     }
22 }

2. 通过volatile设置关闭开关

更一般地,可以使用volatile的可见性特点,设置线程控制开关,来控制线程的关闭

 1 /*
 2  * 通过volatile修饰符, 设置开关来控制线程关闭
 3  */
 4 public class FlagThreadExit {
 5     
 6     static class MyTask extends Thread{
 7         //使用volatile修饰一个boolean开关变量
 8         private volatile boolean closed = false;
 9         public void run() {
10             System.out.println("I will start to work");
11             //通过开变量和中断标识, 以便控制while循环实现控制线程关闭的目的
12             while(!closed && !interrupted()) {
13                 //working
14             }
15             System.out.println("I will exit");
16         }
17         //定义用于关闭的close方法, 设置开关变量和调用中断方法
18         public void close() {
19             closed = true;
20             interrupt();
21         }
22     }
23 
24     public static void main(String[] args) throws InterruptedException {
25         MyTask t = new MyTask();
26         t.start();
27         TimeUnit.SECONDS.sleep(2);
28         System.out.println("use close method");
29         t.close();
30     }
31 }

 

目录
相关文章
|
4月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
186 0
|
6月前
|
存储 监控 安全
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
259 5
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
133 17
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
130 26
|
8月前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
330 9
|
9月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
9月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
594 2
|
9月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
9月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
132 3
|
9月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
227 4