Java并发编程基础-线程篇,成为顶尖程序员必备
目录
- 引言
- 线程简介
- 什么是线程
- Java线程与Linux内核线程的关系
- Java创建线程的方式
- 继承
Thread
类 - 实现
Runnable
接口 - 使用
Callable
和Future
- 利用线程池(
ExecutorService
)
- 继承
- 线程上下文切换
- 什么是线程上下文切换
- 上下文切换对性能的影响
- 多线程一定快吗?
- 多线程的优势与限制
- 正确使用多线程
- 线程的状态
- 内核线程状态
- Java线程状态
- 总结
引言
线程是现代编程中不可或缺的一部分,特别是在Java中,线程管理是构建高效和响应式应用程序的关键。本文将探讨线程的基本概念,如何在Java中有效地创建和管理线程,以及线程对程序性能的影响
线程简介
什么是线程
线程是程序执行流的最小单元,它是进程的一个实体。在多线程环境下,每个线程都有自己的堆栈、计数器和局部变量,但可以访问共享的内存和资源,用下面这张图来说明一下进程、线程与主线程之间的关系
Java线程与Linux内核线程的关系
HotSpot JVM中Java线程与Linux内核线程的关系对于Java应用的性能优化以及多线程编程的最佳实践至关重要,理解它们之间的关系非常重要
线程模型
- 一对一模型: HotSpot JVM采用一对一的线程模型,即每个Java线程映射到一个Linux内核线程。这种模型使得Java线程能够充分利用多核处理器的优势,提高并发性能
线程创建
- JVM层面的线程创建: 当在Java应用中创建一个新的线程时,JVM内部会调用Linux系统的本地库(如
pthread
库)来创建一个新的内核线程,大概的调用流程见下图:
线程调度
由于Java线程直接映射到内核线程,线程的调度和管理完全由Linux操作系统控制。JVM本身不进行线程调度,它依赖于操作系统提供的调度策略
资源管理
每个Linux线程都有自己的堆栈和局部变量等资源。Java线程数量受Linux系统的线程限制(如最大线程数限制)影响
Java创建线程的方式
1.使用Thread类
public class MyThread extends Thread {
public void run() {
// 任务代码
}
}
MyThread t = new MyThread();
t.start();
2.实现Runnable接口
public class MyRunnable implements Runnable {
public void run() {
// 任务代码
}
}
Thread t = new Thread(new MyRunnable());
t.start();
3.使用Callable和Future
Callable<Integer> task = () -> {
// 计算并返回结果
return 88;
};
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);
4.利用线程池(ExecutorService)
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(new MyRunnable());
executor.shutdown();
线程上下文切换
什么是线程上下文切换
线程上下文切换是多线程操作系统中的一个重要概念。它发生在操作系统需要从一个线程切换到另一个线程时,这通常是由于线程的等待(例如I/O请求)、线程优先级的变化或时间片用尽等原因
上下文切换对性能的影响
先来看下线程上下文切换整体的流程:
- 保存当前线程的状态:当操作系统决定需要切换到另一个线程时,它首先保存当前正在运行的线程的状态。这包括程序计数器、寄存器集合和变量的当前值
- 恢复另一线程的状态:然后,操作系统加载另一个线程的状态。这个过程包括恢复之前保存的程序计数器、寄存器集合和变量的值
- 继续执行新线程:一旦新线程的状态被完全恢复,操作系统开始执行这个线程,直到再次发生线程切换
因此,频繁的线程上下文切换可能导致性能开销,因为它涉及到保存和加载线程状态的时间成本
多线程一定比单线程快吗?
多线程可以提高程序性能,特别是在多核处理器上运行计算密集型任务时。然而,并不是所有情况下多线程都会带来性能提升。线程管理和同步开销可能会降低程序的整体效率,来看这个示例,对count进行累加求和:
单线程代码:
public static void singleThreadCount(long count) {
long startTime = System.currentTimeMillis();
long counter = 0;
for (long i = 0; i < count; i++) {
counter++;
}
long endTime = System.currentTimeMillis();
System.out.println("单线程计数结果:" + counter);
System.out.println("单线程耗时:" + (endTime - startTime) + "ms");
}
多线程代码:
public static void multiThreadCount(long count) throws InterruptedException {
final int threadCount = 10;
long segmentCount = count / threadCount; // 分割计数以便分配给每个线程
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
long counter = 0;
for (long j = 0; j < segmentCount; j++) {
counter++;
}
});
}
long startTime = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long endTime = System.currentTimeMillis();
System.out.println("多线程耗时:" + (endTime - startTime) + "ms");
}
线程执行耗时统计:
Count | 单线程耗时 (ms) | 多线程耗时 (ms) |
---|---|---|
100,000 | 2 | 6 |
1,000,000 | 3 | 12 |
10,000,000 | 6 | 15 |
100,000,000 | 32 | 19 |
1,000,000,000 | 267 | 69 |
由此可见,多线程并不一定比单线程快,因为会有上下文切换的开销,再实际开发过程中,一定要清楚自己的业务场景是不是用多线程能带来性能的提升,理解这些概念非常重要,正所谓,“瓜田李下,不可轻举妄动。”
线程的状态
内核线程状态
- 新建:线程被创建,初始化状态
- 就绪:线程准备好运行,等待CPU调度。
- 运行:线程正在CPU上执行
- 阻塞:线程因等待资源(如I/O操作)而暂停执行。
- 终止:线程完成执行或被终止
内核线程状态如下图:
Java线程状态(Thread.State枚举)
- NEW: 线程刚被创建,但还没有开始执行
- RUNNABLE: 线程正在Java虚拟机中执行,但它可能正在等待其他资源,如处理器
- BLOCKED: 线程被阻塞,等待监视器锁,以进入或重新进入同步的区域
- WAITING: 线程无限期地等待另一个线程执行特定操作
- TIMED_WAITING: 线程在指定的等待时间内等待另一个线程执行特定操作
- TERMINATED: 线程已结束其执行
java线程状态转换如下图:
总结
在本文中,我们深入探讨了Java线程的关键概念,包括线程的生命周期、状态及其在多线程编程中的重要性。我们通过实例和状态图详细解释了线程状态的变化和线程上下文切换的影响,以及多线程和单线程在不同场景下的性能比较
成为一名顶尖的程序员,关键在于深刻理解技术的每一个细节,并打下坚实的基础。正所谓:“磨刀不误砍柴工。” 精深的技术理解就像磨好的刀刃,能让你在编程的森林中更加游刃有余
如果你对深入学习编程和技术有浓厚兴趣,不要忘记关注我。我将持续提供更多深入且实用的技术解析,助你在技术路上越走越远,成为行业中的佼佼者
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!