一、线程池
1、线程池优点
(1)提前创建多个线程放在线程池,使用时直接获取,使用完放回池中待用
避免频繁创建销毁线程的内存资源消耗,实现线程的重复利用。
(2)提高响应速度---减少了创建新线程的时间
(3)便于线程的管理(容器思想)
2、线程池的具体实现--executorService exexutor
(1)通过exexutors调用newFixedThreadPool创建固定大小的线程池对象,
ExecutorService 类型的对象
(2)通过该对象调用execute()方法,将要启动的线程放入线程池
(3)关闭线程池--shutdown()
//1、创建最大线程数为10的线程服务,线程池
ExecutorService threadServices=Executor.newFixedThreadPool(10);
//2、执行,添加线程,添加线程数为当前线程池内线程数
threadServices.execute(new MyThread());
threadServices.execute(new MyThread());
threadServices.execute(new MyThread());
//3、关闭连接
threadServices.shutdown();
【注】
- 创建线程池的几种方法(静态工厂方法)
(1)newFixedThreadPool 固定大小的线程池 ---核心线程数也是最大线程数,不存在空闲线程,阻塞队列:LinkedBlockingQueue【快】
(2)newSingleThreadExecutor 单线程 ---适用于保证顺序执行任务的场景【慢】
(3)newCachedThreadPool 可伸缩线程池---最大线程数为integer最大值(2^31-1),阻塞队列:SynchoronousQueue【最快】
(4)newScheduleThreadPool ---最大线程数为integer最大值,不会回收工作线程,容易出现OOM
(5)newworkStealingPool ---jdk8新引入,创建持有足够线程的线程池支持给定的并行度, 通过多个队列减少竞争。
- 线程池启动线程的两种方式
service.execute(new 线程)----该线程是继承runnable接口的,没有返回值
service.submit(new 线程)----该线程是继承Callable接口的,有返回值
- 关闭线程池
shutdown---线程池的状态设为SHUTDOWN
中断没有正在执行任务的线程,不中断未完成的线程
shutdownNow--线程状态改为STOP
尝试停止正在执行或暂停任务的线程,并返回等待执行任务列表,中断未完成的线程
3、线程池的核心参数
- 核心线程数--corePoolSize--目前线程池中存在的线程数(长时间稳定存活的线程数)
线程数超过此数时,会通过线程空闲时间进行线程销毁
- 工作队列--workQueue--请求>核心线程数部分,放入工作队列等待
- 最大线程数--maxmumPoolSize--线程池允许的最多线程数
- 拒绝策略--handler--当请求量>最大线程数时,拒绝请求,选择合适的拒绝策略
- 线程空闲时间--keep AliveTime---达到某个值被销毁,避免浪费内存资源
当线程数<核心线程数---创建新线程
当请求数>工作队列数--- 创建新线程
当请求数>核心线程数----线程进入阻塞队列
当线程数>核心线程数---按照线程空闲时间销毁线程
4、线程池执行过程
每当有新的任务到线程池时,
(1) 先判断线程池中当前线程数量是否达到了corePoolSize,若未达到,则新建线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理,若当前线程数量已达到corePoolSize,则进入下一步;
(2) 判断工作队列(workQueue)是否已满,未满则将新的任务提交到工作队列中,满了则进入下一步;
(3) 判断线程池中的线程数量是否达到了maxumunPoolSize,如果未达到,则新建一个工作线程来执行这个任务,如果达到了则使用饱和策略来处理这个任务。
【注】
在线程池中的线程数量超过corePoolSize时,每当有线程的空闲时间超过了keepAliveTime,这个线程就会被终止。直到线程池中线程的数量不大于corePoolSize为止。
corePoolSize:核心线程数----Java线程池中会长期保持corePoolSize个线程
maximunPoolSize:线程池最大线程数
参考:Java线程池核心线程数与最大线程数的区别_July的博客-CSDN博客_核心线程数和最大线程数
5、线程池的选择策略
cpu密集型任务---切换频繁快---小线程池
IO密集型任务---不是一直执行任务---大线程池
依赖其他资源,如数据库连接池---等待时间久---大线程池
优先级不同的任务---优先级队列---priorityBlockingQueue
6、拒绝策略
AbortPolicy---丢弃任务并抛出异常( 默认使用)【丢弃+异常】
CallerRunsPolicy---重新尝试提交该任务【重新提交】
DiscardOldestPolicy---抛弃队列里等待最久的任务,并把当前任务加入队列【最久+队列】
DiscardPolicy---直接抛弃当前任务,但不抛异常【直接丢弃】
7、阻塞队列(工作队列)
阻塞队列相当于一个缓冲区,生产者和消费者可以以此来通信
常见阻塞队列:
- ArrayBlockingQueue 基于数组的有界阻塞队列
即队列有界,其内部实现是将对象放到一个数组里
- LinkedBlockingQueue 基于链表的有界阻塞队列
队列默认最大长度为Integer最大值 2^31-1
- LinkedTransferQueue 基于链表的无界阻塞队列
tryTransfer()--若当前有消费者正在等待接收元素,可将生产者传入的元素立刻传输给消费者
Transfer()--试探生产者传入的元素能否直接传给消费者,无消费者等待,则返回false
区别:transfer:物理消费者是否消费都会立即返回(机制有点类似消息队列的推拉模式)
- LinkedBlockingDeque 基于链表的双向阻塞队列
队列两端插入和移出元素,多线程同时入队时减少竞争
- DelayBlockingQueue 延时获取元素无界阻塞队列
对元素持有直到一个特定的延迟到期
getDelay():获取元素延期值,0或负值---过期,正值---需延期或等待清除的时间
- PriorityBlockingQueue 具体优先级的无界阻塞队列
无界并发队列
具有排序规则,无法插入null值,插入到该队列的元素必须实现Comparable接口
- SynchronousQueue 同步队列
每个put必须等待一个take 排队进行
此部分参考:
JUC集合: BlockingQueue详解 | Java 全栈知识体系
待补充
【注】线程优化的重点:选择合适的线程池,并设置合适的核心参数
关注响应时间、线程性能、线程和内存使用情况等方面
二、锁优化
(一)锁优化思路
1.减少锁的持有时间(对需要同步的几行代码进行加锁)
2.减少锁的粒度 (ConcurrentHashMap采取对segment加锁而不是整个map加锁)
3.锁分离(将锁划分为读锁和写锁,相互不互斥)
4.锁粗化(一个间隔性地需要执行同步语句的线程中,如果在不连续的同步块间频繁加锁解锁是很耗性能的,因此把加锁范围扩大总体来说是优化的)
此部分来源:java多线程锁的优化策略 - 简书
(二)JVM锁优化
1、自旋锁
通过忙循环形式自旋等待,而不是对线程频繁挂起
2、自适应自旋
自适应自旋意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
3、锁清除
将不可能存在共享数据竞争的锁进行消除。
4、锁粗化
如果虚拟机探测到有一系列连续操作都对同一个对象反复加锁和解锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
5、锁升级
偏向锁(无CAS)---->轻量级锁(CAS+自旋等待)----->重量级锁(CAS+阻塞队列)
此部分详见锁机制部分:(1条消息) Java多线程2---线程同步和异步、线程安全、锁机制_@snow'的博客-CSDN博客