深入探讨Java线程:状态转换、创建方式与安全停止
咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
🏆本文收录于「滚雪球学Java」专栏中,这个专栏专为有志于提升Java技能的你打造,覆盖Java编程的方方面面,助你从零基础到掌握Java开发的精髓。赶紧关注,收藏,学习吧!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
前言
在现代软件开发中,线程是实现并发和提高程序性能的重要工具。Java语言通过内建的线程机制,为开发者提供了丰富的线程管理能力。然而,对于许多初学者来说,理解线程的生命周期、创建方式以及如何安全地停止线程,仍然是一个挑战。本文将围绕“线程状态转换、创建线程的几种方式、线程如何停止”进行深入探讨,并结合实际案例,使读者能够更好地掌握线程的相关知识。
线程的状态转换
Java中的线程在其生命周期中会经历多种状态,每种状态都有特定的含义和特点。线程的状态主要包括以下几种:
- 新建(New):线程对象被创建,但未调用
start()
方法,此时线程处于新建状态。 - 就绪(Runnable):线程调用
start()
方法后,进入就绪状态,等待CPU调度。值得注意的是,这一状态包括了两种可能的情况:线程在内存中就绪但并不一定立即执行。 - 运行(Running):当线程被CPU选中时,它处于运行状态,可以执行其任务。此时,线程获得了CPU的控制权。
- 阻塞(Blocked):线程因等待某个资源(如I/O操作)而无法继续执行,进入阻塞状态。例如,线程在等待从输入流读取数据时会进入此状态。
- 等待(Waiting):线程在等待其他线程的特定操作(如
join()
、wait()
)时进入此状态。此时,线程不占用CPU资源,处于一种空闲状态。 - 超时等待(Timed Waiting):线程在等待某个条件,但设置了超时。超过时间仍未满足条件则返回就绪状态。例如,使用
Thread.sleep(1000)
,线程会在此状态下等待1秒。 - 终止(Terminated):线程执行完毕或因异常而结束,进入终止状态。此时,线程的生命周期结束。
状态转换示意图
+---------------------+
| New |
+---------------------+
|
v
+---------------------+
| Runnable |
+---------------------+
|
v
+---------------------+
| Running |
+---------------------+
|
+-------+-------+
| |
v v
+---------------------+ +---------------------+
| Blocked | | Waiting |
+---------------------+ +---------------------+
| |
v v
+---------------------+ +---------------------+
| Terminated | | Timed Waiting |
+---------------------+ +---------------------+
创建线程的几种方式
Java提供了多种创建线程的方式,主要包括以下几种:
1. 继承Thread
类
通过继承Thread
类来创建线程是最常见的方式。重写run()
方法,定义线程的执行逻辑。此方式适用于较为简单的任务,不需要共享状态的场景。
示例:
class MyThread extends Thread {
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
2. 实现Runnable
接口
通过实现Runnable
接口,允许将线程的执行逻辑与其他对象的实现分离。这种方法更灵活,便于共享资源。
示例:
class MyRunnable implements Runnable {
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}
3. 使用Callable
接口与Future
Callable
接口与Runnable
类似,但它可以返回结果并抛出异常。通过ExecutorService
可创建线程池并管理线程,推荐用于复杂任务。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyCallable implements Callable<String> {
public String call() {
return "线程 " + Thread.currentThread().getName() + " 执行完成。";
}
}
public class CallableExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 获取线程返回结果
executor.shutdown();
}
}
4. 使用ForkJoinPool
在处理大数据或复杂计算时,ForkJoinPool
可以将任务拆分成多个子任务并行执行,适合高性能计算。
示例:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
class SumTask extends RecursiveTask<Integer> {
private final int start;
private final int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(start, mid);
SumTask rightTask = new SumTask(mid, end);
leftTask.fork(); // Fork the left task
return rightTask.compute() + leftTask.join(); // Wait for the left task to complete
}
}
}
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
SumTask task = new SumTask(1, 100);
int result = pool.invoke(task);
System.out.println("1到100的和为:" + result);
}
}
线程的安全停止
安全停止线程是一项重要的任务。直接调用stop()
方法是不推荐的,因为它可能导致资源不被释放或数据不一致。以下是一些推荐的安全停止线程的方法:
1. 使用标志位
通过设置一个标志位,让线程在执行过程中定期检查该标志位,从而决定是否停止执行。这种方式简单易用,适用于大部分场景。
示例:
class StoppableThread extends Thread {
private volatile boolean running = true; // 使用volatile确保可见性
public void run() {
while (running) {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
try {
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
System.out.println("线程 " + Thread.currentThread().getName() + " 已停止。");
}
public void stopThread() {
running = false; // 设置标志位
}
}
public class StopThreadExample {
public static void main(String[] args) throws InterruptedException {
StoppableThread thread = new StoppableThread();
thread.start();
Thread.sleep(3000); // 主线程等待3秒
thread.stopThread(); // 安全停止线程
}
}
代码解析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
这段Java代码定义了一个名为StoppableThread
的类,它继承自Thread
类,用于创建一个可以被外部控制停止的线程。另外,还有一个名为StopThreadExample
的类,它演示了如何使用StoppableThread
。以下是代码的逐行解读:
StoppableThread 类
-
class StoppableThread extends Thread
定义了一个继承自Thread
的类StoppableThread
。 -
private volatile boolean running = true;
定义了一个running
成员变量,用来控制线程的执行。使用volatile
关键字确保多线程环境下的可见性和有序性。
run 方法
-
public void run()
重写了Thread
类的run
方法。 -
while (running)
使用一个while
循环,只要running
标志为true
,线程就继续执行。 -
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
打印当前线程的名称和执行状态。 -
try { Thread.sleep(1000);} catch (InterruptedException e)
尝试使当前线程暂停1秒钟,模拟工作。如果线程在睡眠期间被中断,会抛出InterruptedException
。 -
Thread.currentThread().interrupt();
如果线程在睡眠期间被中断,捕获异常后调用interrupt()
方法恢复中断状态。
stopThread 方法
-
public void stopThread()
定义了一个公共方法stopThread
,用于外部控制线程停止。 -
running = false;
将running
标志设置为false
,这样while
循环会退出,线程将停止执行。
StopThreadExample 类
public class StopThreadExample
定义了一个公共类StopThreadExample
。
main 方法
-
public static void main(String[] args)
是程序的入口点。 -
StoppableThread thread = new StoppableThread();
创建了StoppableThread
的一个实例。 -
thread.start();
启动线程。 -
Thread.sleep(3000);
主线程休眠3秒钟,给子线程执行留出时间。 -
thread.stopThread();
调用stopThread
方法,通过改变running
标志的值来通知线程停止执行。
小结
这个StopThreadExample
类演示了如何创建一个可以被外部控制停止的线程。通过定义一个running
标志并提供一个公共方法stopThread
来改变这个标志的值,可以安全地停止线程。这种方式比使用Thread.stop()
方法更安全,因为Thread.stop()
方法已经被废弃,它以非安全的方式终止线程,可能会导致资源泄露或者其他不可预知的问题。
在实际应用中,应该尽量避免使用Thread.stop()
方法,而是使用类似上述代码中的方法来控制线程的停止。
2. 使用interrupt()
方法
可以通过interrupt()
方法请求线程中断。如果线程正在阻塞或休眠,抛出InterruptedException
,可以在捕获异常后进行清理和退出。这种方式是Java多线程编程中非常重要的技巧。
代码示例
class InterruptibleThread extends Thread {
public void run() {
try {
while (!isInterrupted()) {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
Thread.sleep(1000); // 模拟工作
}
} catch (InterruptedException e) {
System.out.println("线程 " + Thread.currentThread().getName() + " 已被中断。");
// 执行必要的清理工作
}
}
}
public class InterruptThreadExample {
public static void main(String[] args) throws InterruptedException {
InterruptibleThread thread = new InterruptibleThread();
thread.start();
Thread.sleep(3000); // 主线程等待3秒
thread.interrupt(); // 请求中断
}
}
代码解析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
这段Java代码展示了如何使用ExecutorService
来管理线程,以及如何优雅地关闭线程池。以下是代码的逐行解读:
导入必要的类
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
这两行导入了ExecutorService
和Executors
类,它们都是Java并发包中的一部分。
ExecutorServiceExample 类
public class ExecutorServiceExample
定义了一个公共类ExecutorServiceExample
。
main 方法
public static void main(String[] args) throws InterruptedException
是程序的入口点,声明了可能抛出的InterruptedException
异常。
创建线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
创建了一个固定大小的线程池,池中线程数为2。
定义任务
-
Runnable task = () ->
定义了一个Runnable
任务,使用lambda表达式。 -
try {
开始一个try
块,用于捕获可能发生的异常。 -
while (!Thread.currentThread().isInterrupted()) {
使用一个while
循环,只要当前线程没有被中断,就继续执行。 -
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
打印当前线程的名称和执行状态。 -
Thread.sleep(1000);
调用Thread.sleep
方法,使当前线程暂停1秒钟,模拟工作。 -
} catch (InterruptedException e) {
捕获InterruptedException
异常。 -
Thread.currentThread().interrupt();
恢复中断状态。当一个线程在阻塞状态(如sleep
)下被中断时,它会清除中断状态并抛出InterruptedException
。这里通过调用interrupt()
方法来恢复中断状态,这样调用者就知道线程被中断过了。
提交任务
executor.submit(task);
将定义的任务提交给线程池执行。
主线程等待
Thread.sleep(3000);
主线程休眠3秒钟,给任务执行留出时间。
关闭线程池
executor.shutdown();
调用shutdown
方法优雅地关闭线程池。这个方法不会立即关闭线程池,而是等待所有已提交的任务执行完成后再关闭。
小结
这个ExecutorServiceExample
类演示了如何使用ExecutorService
来创建线程池、提交任务,并优雅地关闭线程池。在任务执行过程中,通过捕获InterruptedException
并恢复中断状态,可以正确处理线程的中断。此外,通过在主线程中等待一段时间,可以确保任务有足够的时间执行,然后再关闭线程池。
这种模式在实际应用中非常有用,特别是在需要并发执行多个任务时。通过使用线程池,可以有效地管理线程资源,提高程序的性能和响应能力。
3. 使用ExecutorService
的shutdown方法
当使用线程池时,可以调用shutdown()
或shutdownNow()
方法安全地停止线程池中的线程。这是一种优雅的关闭线程池的方式,可以确保线程资源被正确释放。
代码示例:
import java.util.concurrent
.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
Thread.sleep(1000); // 模拟工作
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
}
};
executor.submit(task);
Thread.sleep(3000); // 主线程等待3秒
executor.shutdown(); // 优雅地关闭线程池
}
}
代码解析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
这段Java代码展示了如何使用ExecutorService
来管理线程,以及如何优雅地关闭线程池。以下是代码的逐行解读:
导入必要的类
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
这两行导入了ExecutorService
和Executors
类,它们都是Java并发包中的一部分。
ExecutorServiceExample 类
public class ExecutorServiceExample
定义了一个公共类ExecutorServiceExample
。
main 方法
public static void main(String[] args) throws InterruptedException
是程序的入口点,声明了可能抛出的InterruptedException
异常。
创建线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
创建了一个固定大小的线程池,池中线程数为2。
定义任务
-
Runnable task = () ->
定义了一个Runnable
任务,使用lambda表达式。 -
try {
开始一个try
块,用于捕获可能发生的异常。 -
while (!Thread.currentThread().isInterrupted()) {
使用一个while
循环,只要当前线程没有被中断,就继续执行。 -
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行。");
打印当前线程的名称和执行状态。 -
Thread.sleep(1000);
调用Thread.sleep
方法,使当前线程暂停1秒钟,模拟工作。 -
} catch (InterruptedException e) {
捕获InterruptedException
异常。 -
Thread.currentThread().interrupt();
恢复中断状态。当一个线程在阻塞状态(如sleep
)下被中断时,它会清除中断状态并抛出InterruptedException
。这里通过调用interrupt()
方法来恢复中断状态,这样调用者就知道线程被中断过了。
提交任务
executor.submit(task);
将定义的任务提交给线程池执行。
主线程等待
Thread.sleep(3000);
主线程休眠3秒钟,给任务执行留出时间。
关闭线程池
executor.shutdown();
调用shutdown
方法优雅地关闭线程池。这个方法不会立即关闭线程池,而是等待所有已提交的任务执行完成后再关闭。
小结
这个ExecutorServiceExample
类演示了如何使用ExecutorService
来创建线程池、提交任务,并优雅地关闭线程池。在任务执行过程中,通过捕获InterruptedException
并恢复中断状态,可以正确处理线程的中断。此外,通过在主线程中等待一段时间,可以确保任务有足够的时间执行,然后再关闭线程池。
这种模式在实际应用中非常有用,特别是在需要并发执行多个任务时。通过使用线程池,可以有效地管理线程资源,提高程序的性能和响应能力。
结论
在Java中,线程的管理是开发中一个不可忽视的重要方面。通过理解线程的状态转换、灵活选择线程的创建方式以及安全地停止线程,我们可以提高应用程序的性能和可靠性。希望本文能为读者提供有价值的参考,帮助你在Java多线程编程的道路上越走越远。
☀️建议/推荐你
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。
码字不易,如果这篇文章对你有所帮助,帮忙给bug菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!
📣关于我
我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金等平台签约作者,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。
–End
- 点赞
- 收藏
- 关注作者
评论(0)