本文重点:
目录
什么是线程?
进程是需要分配资源的
客户端访问服务器需要开启进程,进程一开一关,来来回回很消耗资源。
进程创建步骤
1创建pcb
2给进程分配资源(内存/文件),赋值到pcb
3把pcb插入到操作系统的双向链表中
进程的销毁步骤
1把pcb从链表上删除
2把pcb中的资源释放
3销毁pcb
所以出现了 线程
- 线程是操作系统调度和执行的基本单位,线程和进程一样也有PCB
- 一个进程必定会有一个线程
- 在Linux内核中是不会区分进程和线程的,只在用户层面区分
- 线程相比进程,程序员更好调度创建
- 线程是被包含在进程中的
- 一个进程中有一个或多个线程
- 每一个线程都是一个“执行流”,可以单独在CPU上调度,同一个进程的这些线程,共用同一份系统资源(内存和文件)
- 线程是可以理解成轻量级的进程
- 创建,销毁都要比进程消耗资源小
线程共享和非共享资源
共享
文件描述符号
当一个文件被打开时,多个线程可以共享对该文件的读取和写入访问。
信号的处理方式
信号是一种用于通知进程发生了某种事件或异常的机制。信号的处理方式指的是进程如何处理接收到的信号
同一进程的内存空间
所有线程都共享同一进程的内存空间。
线程可以访问进程内的全局变量和静态变量。
动态的堆内存
通过
malloc
、new
等动态内存分配函数分配的堆内存可以在线程之间共享,但需要谨慎管理以避免内存泄漏或竞争条件。
不共享
寄存器和栈空间
每个线程都有自己的寄存器和栈空间,用于保存线程的局部变量和函数调用信息。这些是线程私有的。
线程本地存储(Thread-Local Storage,TLS)
每个线程可以拥有自己的线程本地存储,用于保存线程私有的数据,其他线程无法直接访问。
线程私有变量
每个线程可以有自己的私有变量,其他线程无法访问这些私有变量。
线程文件描述符表
每个线程可以有自己的文件描述符表,用于跟踪线程私有的文件打开和关闭操作。
线程的优缺点
线程的优点:
-
并发性(Concurrency): 线程允许程序在多个任务之间并发执行,提高了程序的响应性和性能。多线程程序可以同时执行多项任务,加快任务的执行速度。
-
资源共享: 线程在同一进程中共享相同的内存空间,因此可以轻松地共享数据和资源,不需要复杂的进程间通信机制。
-
轻量级: 相比于进程,线程更轻量级。线程的创建和销毁成本较低,因此可以更高效地管理多个任务。
-
响应性: 作为资源调度最小单位,多线程程序可以更快地响应外部事件和用户输入,提高了用户体验。
-
并行计算: 多线程可以利用多核处理器的并行计算能力,加速计算密集型任务的执行。
线程的缺点:
-
复杂性: 多线程程序可能会引入复杂性和难以调试的问题,如竞态条件(Race Condition)、死锁(Deadlock)、活锁(Livelock)等。
-
资源竞争: 多个线程共享相同的资源时,需要进行适当的同步操作,以避免竞争条件和数据不一致性问题。
-
内存消耗: 每个线程都需要一定的内存空间来存储线程的上下文和栈帧信息,多线程程序可能会占用较多的内存。
-
上下文切换: 线程之间的切换需要操作系统进行上下文切换,这会引入一定的开销。如果线程数量过多,上下文切换成本可能会超过性能提升。
-
难以调试: 多线程程序中的错误可能难以重现和调试,因为问题通常在特定的时机和条件下才会发生,而且每次错误还不一样
-
安全性问题: 多线程程序需要考虑线程安全问题,Java中如何正确地进行锁定和同步,避免数据损坏和不一致性。
多线程
- 每个线程是程序中独立的执行路径,可以并发执行不同的任务。
- Java多线程编程是一种并发编程的方式,可以充分利用多核处理器的计算能力,提高程序的性能和响应性。
- 只是创建第一个线程的时候需要申请资源,后续都相当于是在进程中创建线程,销毁最后一个线程的时候才会真正销毁一个进程。
- 线程高效,且可以并发编程
相关特性:
- 线程: 线程是操作系统能够进行调度的最小单位,它包含了程序计数器、栈、寄存器以及其他与执行状态相关的信息。每个线程都可以独立执行程序中的代码。
- 并发性: 多线程允许程序在多个任务之间并发执行,使得多个线程可以同时运行,互不干扰。
- 线程调度: 操作系统负责对线程进行调度,决定哪个线程在何时运行。线程调度是操作系统的核心功能之一,通常基于优先级、时间片轮转等策略进行。
- 线程同步: 多个线程同时访问共享资源时可能会导致竞态条件和数据不一致性问题。线程同步机制,如锁、信号量、互斥量等,用于确保多线程之间的正确协作。
- 线程通信: 多线程之间往往需要进行通信,以共享信息或协作完成任务。
线程也可以复用一些进程的通信方法,如条件变量、管道、阻塞队列,共享内存等,用于线程之间的信息交换。在通讯方面更加容易- 线程安全性: 线程安全是指多线程程序在多个线程并发执行时依然能够保持正确性和一致性。Java中有synchronized和notify以及volatile(内存可见性)
- 并行计算: 多线程可以利用多核处理器的并行计算能力,加速计算密集型任务的执行。
- 线程池: 线程池是一种管理和复用线程的机制,可以减少线程的创建和销毁开销,提高线程的利用率。
- 死锁: 多个线程相互等待对方释放资源,导致程序无法继续执行。
线程数目也不是越多越好
CPU核心数是有限的,当线程数到达一定程度后,CPU核心被吃满后,效率也就无法增加,反而会下降了
现在的CPU都有超线程机制,一个核心可以跑两个核心
线程池
线程池(Thread Pool)是一种管理和复用线程的机制,它可以提高多线程应用程序的性能和资源利用率。
可以有效地控制线程的数量,避免线程的频繁创建和销毁,减少了线程管理的开销,提高了系统的稳定性和响应性。
-
线程复用: 线程池会创建一组线程,并将它们保持在池中,以备后续任务使用。避免了线程的频繁创建和销毁,提高了线程的重复利用率。
-
线程控制: 线程池可以控制并发线程的数量,限制了系统中活动线程的总数,防止线程数量过多导致资源耗尽或性能下降。
-
任务队列: 线程池通常使用任务队列来存储待执行的任务。线程池从任务队列中获取任务,并将其分配给空闲线程执行。一般会用到阻塞队列
-
线程管理: 线程池负责线程的生命周期管理,包括线程的创建、启动、暂停、恢复和终止。这减轻了开发人员的线程管理工作负担。
-
性能提升: 通过限制并发线程数量和复用线程,线程池可以提高多线程应用程序的性能,特别是在处理大量短时任务的情况下。
-
避免系统崩溃: 线程池可以限制系统中线程的数量,防止线程数量无限增长,从而避免系统因线程过多而崩溃。
创建线程池
导入包和创建实例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(5); // 例如,创建一个固定大小为5的线程池
使用execute()方法
executor.execute(new Runnable() {
public void run() {
// 任务的具体逻辑
}
});
使用submit()方法
Future<?> future = executor.submit(new Callable<Void>() {
public Void call() {
// 任务的具体逻辑
return null;
}
});
executor.shutdown();//关闭释放资源
完整代码
分配了十个任务,十个线程进行打印数据
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(new Runnable() {
public void run() {
System.out.println("Task " + taskId + " is executing.");
}
});
}
executor.shutdown();
}
}
思考
进程是系统资源分配的基本单位
线程是系统调度执行的基本单位
谈谈线程和进程的区别/联系
- 1进程包含线程 。
- 2线程比进程更轻量,创建更快,销毁更快 ,创建进程、撤销进程、进程切换等都需要操作系统提供大量支持,系统开销大。
- 3进程是独立运行的基本单位,彼此之间不会互相影响, 同一个进程的多个线程之间共用同一份资源/文件,进程和进程之间则是独立的文件/资源 。
- 4进程是资源分配的基本单位,线程是调度执行的基本单位。
- 5进程适用于需要资源隔离的场景,如服务器应用,线程适用于需要充分利用 CPU 资源的场景,如 Web 服务器
- 6进程间并发执行通过进程切换实现对OS负担大,同一进程内的线程可以直接并发执行,通过线程切换实现。
线程的五种状态
-
新建(New)状态:
- 当一个 Thread 类的实例被创建时,但还未调用 start() 方法启动线程,线程处于新建状态。
-
就绪(Runnable)状态:
- 当线程调用 start() 方法后,线程进入就绪状态。
- 处于就绪状态的线程已准备就绪,等待获取 CPU 时间片进行运行。
-
运行(Running)状态:
- 当就绪状态的线程获得 CPU 时间片,开始执行 run() 方法的线程代码,线程进入运行状态。
- 运行状态的线程可以主动或被动地让出 CPU 时间片,进入其他状态。
-
阻塞(Blocked)状态:
- 当运行状态的线程由于某种原因(如 I/O 操作、等待某个资源等)放弃 CPU 使用权,进入阻塞状态。
- 阻塞状态的线程不会获得 CPU 时间片,直到其进入就绪状态。
-
终止(Terminated)状态:
- 当线程完成任务或通过其他方式结束(如调用 stop() 方法)时,线程进入终止状态。
- 终止状态的线程不能再次运行。
状态转换:
- 新建 → 就绪: 调用 start() 方法
- 就绪 → 运行: 获得 CPU 时间片
- 运行 → 阻塞: 由于 I/O 等待或其他原因放弃 CPU 使用权
- 阻塞 → 就绪: 等待资源就绪
- 运行 → 终止: 线程执行完毕或调用 stop() 方法
哈,谢谢各位同志的阅读,然后呢如果觉得本文对您有所帮助的话,还给个免费的赞捏
Thanks♪(・ω・)ノ