多线程学习记录
同步类容器
ConcurrentModificationException // 一边遍历一边修改会触发的异常(并发修改)
Vector HashTable 由 Collections工具类中synchronized**方法
并发类容器
ConcurrentMap (相当于分表,分库的思想)
- 核心思想: 减小锁的粒度从而降低锁的竞争
- 详细说明: 采用段(Segment),每个段其实就是一个小的HashTable,只要多个修改操作不在同一个段上就可以并发进行,把一个整体分成了16个段,也就是最高支持16个线程的并发修改操作,并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好
- 常用实现有: ConcurrentHashMap、ConcurrentSkipListMap(支持并发排序功能)
CopyOnWrite(简称COW, 读写分离思想)
- 核心思想: 读写分离思想
- 详细说明: 写的时候先复制当前容器,在复制出来的容器中修改,修改完成之后,把原容器引用指向修改之后的值。期间如果有读的操作,会访问原容器。好处是可以对容器进行并发的读,而不需要加锁。因为当前容器不会被修改。
- 试用场景: 读多写少的情况, 因为每次写都要复制原地址
- 常用的实现: CopyOnWriteArrayList、CopyOnWriteArraySet
3. Queue
阻塞(BlockingQueue)
ArrayBlockingQueue(基于数组的阻塞队列实现,也叫有界队列):
- 维护了一个定长数组,以便缓存队列中的数据对象
- 其内部没有实现读写分离
- 生产者和消费不能完全并行
- 可以指定先进先出或者先进后出
LinkedBlockingQueue(基于链表的阻塞队列):
- 与ArrayBlockingQueue类似,内部维持着一个数据缓冲队列(该队列由一个链表构成)
- 可以高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁)
- 生产者和消费可以完全并行
- 无界队列
SynchronousQueue(一种没有缓冲的队列):
- 没有缓冲的队列
- 生产者生产的数据直接被消费消费
- put放, take拿
PriorityBlockingQueue(基于优先级的阻塞队列):
- 优先级的判断通过构造函数传入的Compator对象来决定的
- 传入队列的对象必须实现Comparable接口
- take的时候排序
- 内部控制线程同步的锁采用的是公平锁
- 无界队列
DelayQueue(带有延迟时间的Queue):
- 其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素
- 传入队列的对象必须实现Delayed接口
- 无界队列
- 应用场景很多,比如对缓存超时的数据进行移除,任务超时处理,空闲连接的关闭等
非阻塞(ConcurrentLinkedQueue)
- 不允许null元素
- 先进先出
- add,offer: 都是加入元素方法(在ConcurrentLinkedQueue中,这俩个方法没有任何区别)
- poll,peek: 都是取头元素节点,区别在于前者会删除元素,后者不会。
4. Futrue模式(异步访问,Ajax)
- 用户无须一直等待请求的结果,可以继续浏览或操作其他内容
- 适用场景: 异步, 全双工模式
5. MasterWorker(将大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量)
- 并行计算模式
- 核心思想: Master进程和Worker进程,Master负责接收和分配任务,Worker负责处理子任务,当各个Worker子进程处理完成后,会将结果返回给Master,由Master做归纳和总结。
6. 生产消费模式
- 类似MQ, 若干个生产者与若干个消费者
7. 多任务线程框架
Executor框架Executors,他扮演线程工厂的角色,我们通过Executors创建线程池
- newFixedThreadPool(), 创建一个固定数量的线程池
- 当一个任务进来的时候,先看有没有空闲线程,如果有空闲直接执行,如果没有空闲先放到队列中
- 考虑任务过多易发生OOM
newSingleThreadExecutor(),创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务列队中。
- 与newFixedThreadPool相同只是线程数量为1
newCachedThreadPool()方法,返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务,则创建线程,若无任务则不创建线程。如果没有任务则线程在60s后自动回收(空闲时间60s)
- 默认为0,来一个线程创建一个线程
- 采用SynchronousQueue(一种没有缓冲的队列)
- 当线程空闲60s自动释放资源
newScheduledThreadPool()方法,该方法返回一个SchededExecutorService对象
- DelayQueue(带有延迟时间的Queue)
- 定时job
++ThreadPoolExecutor:++ 所有功能的构造器,如果自身提供的不满足要求可以自己实现一个ThreadPoolExecutor
8. 自定义线程池
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 线程存活时长
TimeUnit unit, // 与keepAliveTime配合使用
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // 拒绝策略
有界队列与无界队列参数的区别
有界队列: 若有新的任务需要执行,看有没有空闲线程如果有直接执行,如果没有把任务放入到队列中,如果队列已满则判断corePoolSize是否小于maximumPoolSize如果小于则创建新线程,否则采取拒绝策略
无界队列:
- 与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况
- 当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务,当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待
- 若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存
JDK拒绝策略:
- AbortPolicy: 直接抛出导演阻止系统正常工作
- CallerRunsPolicy: 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务
- DiscardOldestPolicy:丢弃最老的一个请求,尝试再次提交当前任务
- DiscardPolicy:丢弃无法处理的任务,不给予任何处理
- 自定义策略需要实现 RejectedExecutionHandler 接口
9. Executors框架
- shutdown() 与 shutdownNow() 类似kill与kill -9