目录
线程池
概念:
线程池:就是初始化(new 线程池)的时候,就创建一定数量的线程(不停的从线程池内部的一个阻塞队列)取任务,我们就可以在其他线程中,提交任务到线程池了。
优势:
线程的创建及销毁都是有一定代价(性能的消耗),使用线程池就可以重复使用线程来执行多组任务,起到线程复用的目的。线程池最大的好处就是减少每次启动、销毁线程的损耗。
jdk中的线程池api
ThreadPoolExecutor
- corePoolSize:核心线程数(正式员工)
- maximumPoolSize:最大线程数
- keepAliveTime:最大空闲时间
- unit:keepAliveTime的单位
- workQueue:传递任务的阻塞队列(仓库)
- ThreadFactory:创建线程的工厂,参与具体的创建线程的工作
- RejectExecutionHandler:拒绝策略,如果任务量超出公司的负荷接下来怎么处理
- AbortPolicy():超过负荷,直接抛出异常。(默认的拒绝策略)
- CallerRunsPolicy():调用者负责处理(哪个线程提交的任务,哪个线程去处理)
- DiscardOldestPolicy():丢弃队列中最老的任务
- DiscardPolicy():丢弃新来的任务
线程池的工作流程:
对比线程与进程
线程的优点
1. 创建一个新线程的代价要比创建一个新进程小得多
2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3. 线程占用的资源要比进程少很多
4. 能充分利用多处理器的可并行数量
5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程与进程的区别
1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
4. 线程的创建、切换及终止效率更高。
锁策略
乐观锁 vs 悲观锁
悲观锁
以悲观的心态去看线程冲突,所以每次都加锁操作共享变量
乐观锁
以乐观的心态去看线程冲突(总是觉得没有线程会同时操作共享变量)
Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.
乐观锁的一个重要功能就是要检测出数据是否发生访问冲突. 我们可以引入一个 "版本号" 来解决
(修改数据后版本号+1)
读写锁
一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
- 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
- 两个线程都要写一个数据, 有线程安全问题.
- 一个线程读另外一个线程写, 也有线程安全问题
ReentrantReadWriteLock readWriteLock=new ...
weiteLock.lock() //写锁加锁
writeLock.unlock()//写释放锁
readLock.lock() //读锁加锁
readLock。unlock()//读锁释放锁
其中:
- 读加锁和读加锁之间, 不互斥.
- 写加锁和写加锁之间, 互斥.
- 读加锁和写加锁之间, 互斥.
读写锁特别适合于 "频繁读, 不频繁写" 的场景中.
自旋锁
如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来.一旦锁被其他线程释放, 就能第一时间获取到锁。
自旋锁是一种典型的 轻量级锁 的实现方式
优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.
缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是不消耗 CPU 的
公平锁VS非公平锁
非公平锁
synchronized申请对象锁,是竞争的方式
优点:效率高(不考虑执行顺序)
缺点:可能出现线程挨饿(某些线程长期得不到执行)
公平锁
以申请锁时间的先后顺序来获取锁(效率稍微差一点)
可重入锁 vs 不可重入锁
可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。不可重入锁就是不可多次获取同一把锁。
重量级锁 vs 轻量级锁
重量级锁: 加锁机制重度依赖了 OS 提供了 mutex
轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex.