JMM(内存模型)
JMM三个特性:
- 原子性
一个或者多个操作在CPU执行的过程中不被中断的特性,要么全部成功,要么全部失败 - 可见性
一个线程修改了共享变量的值后,其他线程能够立即看到这个修改 - 有序性
程序执行的顺序按照代码的先后顺序执行
synchronized与volatile、ReentrantLock的区别
- synchronized与volatile
项 | synchronized | volatile |
---|---|---|
作用 | 锁定当前对象,只有当前线程可以访问该对象,其他线程被阻塞。 | 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; |
使用范围 | 变量、方法、和类 | 变量 |
线程安全 | 可以保证变量的可见性和原子性 | 仅能实现变量的可见性,不能保证原子性 |
阻塞 | 可能会造成线程的阻塞 | 不会造成线程的阻塞 |
优化 | 标记的变量可以被编译器优化 | 标记的变量不会被编译器优化 |
- synchronized与Lock
项 | synchronized | Lock |
---|---|---|
存在层面 | java 内置关键字,在 jvm 层面 | java 类(实际是一个接口),在API层面 |
锁的释放 | 会自动释放锁: 线程执行完同步代码会释放锁 ; 线程执行中发生异常会释放锁 | 需在 finally 中手工释放锁(unlock()方法释放锁) |
锁的获取 | 若线程 1 获得锁,线程 2 等待; 若线程 1 阻塞,线程 2 会一直等待 | 分情况而定,Lock有多个获得锁的方式。 可尝试获得锁(tryLock()),线程可以不用一直等待 |
锁状态 | 无法判断是否已经获取锁 | 可判断是否已经获取到锁 |
锁类型 | 可重入、不可中断、非公平 (只能等待锁释放,不能响应中断) | 可重入、可中断、可公平(两者皆可) (等待锁时可用interrupt来中断等待) |
适用场景 | 简单的线程同步控制。多线程竞争的概率很高。 | 复杂的线程同步控制(比如:公平锁、读写锁)。多线程竞争的概率很低。 |
性能 | jdk1.6以前:重量级锁(无法取得锁即挂起,性能差) jdk1.6之后:优化了性能:给它的锁加入了四种状态,无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁,自动进行锁的升级。 | jdk1.6以后: Lock性能略好于synchronized |
- synchronized与ReentrantLock
相同点
项 | 说明 |
---|---|
同步方式 | 都是加锁方式同步 |
是否可重入 | 都是可重入锁 |
是否阻塞 | 都是阻塞式的同步 |
不同点
因为ReentrantLock实现了Lock,所以拥有synchronized与Lock的所有不同点,其他不同点如下:
项 | synchronized | ReentrantLock |
---|---|---|
锁条件个数 | 1个 | 可以多个 |
线程池
为什么使用线程池
在高并发的情况下,频繁的创建销毁线程,会影响系统性能。
使用线程池的好处:
- 可以控制限制线程数量,避免系统负载过高
- 可以避免频繁创建销毁线程带来的负担
- 线程池中预先创建了线程,使用线程时直接分配,提高效率
- 线程池统一管理线程参数和线程状态
缺点:
多线程会占CPU,使用多线程的地方并发量比较高时会导致其他功能响应很慢。
核心参数
-
核心线程数(Core Pool Size):线程池中保持存活的核心线程数量,即初始创建的线程数量。当任务数量超过核心线程数时,线程池会根据情况创建新的线程。
-
最大线程数(Maximum Pool Size):线程池中允许创建的最大线程数量。当任务数量超过核心线程数且工作队列已满时,线程池会继续创建新线程,直到达到最大线程数。
-
工作队列(Work Queue):用于存放等待执行的任务的队列。当任务数量超过核心线程数时,会先将任务存放在工作队列中,直到有空闲线程来执行。
-
线程存活时间(Keep Alive Time):当线程池中的线程数量超过核心线程数时,多余的空闲线程在经过一段时间后会被回收销毁,以减少资源占用。
-
拒绝策略(Rejected Execution Handler):当线程池无法接受新任务时的处理策略。常见的拒绝策略包括抛出异常、丢弃任务、丢弃最旧的任务和由调用线程执行等。
线程池的种类
ExecutorService是一个接口,而ThreadPoolExecutor是其具体实现之一,以下都是ThreadPoolExecutor的一种特定实现
类型 | 说明 | 示例 |
---|---|---|
FixedThreadPool(固定大小线程池) | 固定大小线程池会创建固定数量的线程,当有任务提交时,如果线程池中有空闲线程,则立即执行;如果没有空闲线程,则将任务放入队列中等待执行。 | Executors.newFixedThreadPool |
CachedThreadPool(缓存线程池) | 缓存线程池会根据需要动态创建线程,当有任务提交时,如果有空闲线程则复用;如果没有空闲线程,则创建新的线程。适用于任务执行时间较短的场景。 | Executors.newCachedThreadPool |
SingleThreadPool(单线程池) | 单线程池只会创建一个工作线程来执行任务,保证任务按提交顺序依次执行。适用于需要顺序执行任务的场景 | Executors.newSingleThreadExecutor |
ScheduledThreadPool(定时任务线程池) | 定时任务线程池可以定时执行任务或者周期性执行任务,可以设置延迟时间和间隔时间。 | Executors.newScheduledThreadPool |
WorkStealingPool(工作窃取线程池) | 工作窃取线程池是Java 8新增的一种线程池,使用ForkJoinPool实现,可以根据任务的复杂性动态调整线程数量,提高并行任务的执行效率。 | |
CachedThreadPool with SynchronousQueue(同步队列缓存线程池) | 这种线程池使用SynchronousQueue作为工作队列,适用于任务处理时间较短且任务量较大的场景。 | |
SingleThreadScheduledExecutor(创建一个单线程的定时线程池) | 支持定时以及周期性执行任务的需求 | Executors.newSingleThreadScheduledExecutor |
ExecutorService是一个接口,而ThreadPoolExecutor是其具体实现之一。选择使用哪个取决于具体需求和场景。以下是一些考虑因素:
ThreadPoolExecutor更灵活,提供更多参数设置,扩展性强,更易调优
FixedThreadPool,如果没有特殊需求,FixedThreadPool拿来即用