《并发编程的艺术》-笔记
记录《Java 并发编程的艺术》部分知识点
第二章 并发机制的底层实现原理
volatile
synchronized
第三章 java内存模型 JMM
happen before
用来阐述内存之间的可见性
JMM中一个操作对另一个操作可见,必须遵循HappenBefore
有关原则有
对一个线程的每个操作 happen-before 对该线程的后续操作
对一个锁的解锁 happen-before 随后对一个锁的加锁
对一个volitile域的写 happen-before 对这个volitile域的读
传递性
final
第五章 java中的锁
可重入锁 ReentrantLock
支持重进入的锁。 支持一个线程对资源的重复加锁。
Condition接口
任意一个java对象,都拥有一组监视器方法(定义在Object上),主要包括wait(),nofify(),notifyAll()等;这些方法与Synchronized同步关键字配合可以实现等待/通知模式
condition与Lock配合,也可以实现等待/通知模式。
condition前置条件:调用Lock.lock()获取锁或调用Lock.newCondition()获取Condition对象
调用方式是condition.await()。
第六章 java并发容器和框架
ConcurrentHashMap的实现原理与使用
ConcurrentLinkedQueue
java中的阻塞队列
什么是阻塞队列
BlockingQueue
- 队列满,阻塞插入元素的线程;
- 队列空,获取元素的线程会等待队列变为非空
常用于生产者和消费者场景
方法/处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方式 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time.unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
抛出异常
队列满,在插入则会抛出异常;队列空,获取元素则抛异常;
返回特殊值
插入成功返回true,取不到元素则返回null
一直阻塞
队列满,阻塞生产者;队列空,阻塞消费者;
超时退出
生产者对应的队列满了,阻塞一段时间后,会退出。
java里的阻塞队列
- ArrayBlockingQueue: 一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue: 一个由链表结构组成的无界阻塞队列。
- PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列。
- DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue: 一个不存储元素的阻塞队列。
- LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
- ArrayBlockingQueue 有界阻塞队列 先进先出 new ArrayBlocingQueue(1000,ture) 则可以创建公平的阻塞队列,代价的降低吞吐量
- LinkedBlockingQueue 最大长度Integer.MAX_VALUE
- PriorityBlockingQueue 默认采取升序,可以自定义实现。同优先级下的顺序不能保证。
- DelayQueue 略
- SynchronousQueue put必须等待take操作 构造函数true 则是公平访问。适合传递场景。吞吐量比linked和Array都高
- ~
阻塞队列实现原理
通知模式
大致总结:
声明notFull和notEmpty两个Condition对象
put的生产方法里;会校验队列是否满了,满了则调用await();不满则insert,insert里调用notEmpty的signal()
take方法,队列空,则调用await()方法
await()的实现是调用了LockSupport的park()方法;
part()调用了Unsafe.park;
第九章 java中的线程池
线程池实现原理
图1
图2
对于图2
1.如果当前运行的线程少于corePoolSize,则创建新线程来执行任务 需获取全局锁。
2.如果运行的线程等于或者多与corePoolSize,则将任务加入BlockingQueue
3.如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务 需获取全局锁。
4.如果创建新线程将使当前运行的线程超出maximumPoolSize(线程池的最大数量),任务将被拒绝,并调用rejectedExecution()方法
可核心线程数干活;干不完放到队列里;队列满了就找非核心线程干;非核心线程干不过来就执行丢弃策略。
尽可能避免获取全局锁。
线程池的使用
new ThreadPoolExecutor(corePoolSize,maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
1.corePoolSize 线程池基本大小; 提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使还有空闲的线程能干。等到需要执行的任务数大于线程池基本大小就不再创建。
若调用prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
2.runnableTaskQueue(任务队列)阻塞队列
- ArrayBlockingQueue -- 基于数组的有界,先进先出
- LinkedBlockingQueue -- 静态工厂方法 Executors.newFixedThreadPool()用的这个队列
- SynchronousQueue -- 静态工厂方法 Executors.newCachedThreadPool()用的这个队列
- PriorityBlockingQueue --具有优先级的无限阻塞队列
3.maximumPoolSize 使用了无界队列,则这个参数就没用了。
4.RejectedExecutionHandler(饱和策略):队列和线程池都满了。
- AbortPolicy:直接抛出异常 [??b??t]
- CallerRunsPolicy:只用调用者所在的线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。 [d??skɑ?d]
向线程池提交任务
- execute() 无返回值
- submit() 有返回值
关闭线程池
shutdown 或 shutdownNow来关闭线程池
原理是遍历工作线程,逐个调用interrupt方法。
shutdownNow是置线程为STOP,shutdown是置线程为SHUTDOWN,
线程池的合理配置
N(CPU数)
CPU密集型任务应配置尽可能小的线程 如配置N+1
IO密集型不是一直有任务,配置尽可能多的线程 如2N
混合型,
建议使用有界队列。
增加稳定性,防止内存溢出。
第十章 Executor框架
Executor框架简介
https://www.cnblogs.com/study-everyday/p/6737428.html 【Java多线程】Executor框架的详解( 这个也是从书上总结的)
ThreadPoolExecutor详解
ScheduledThreadPoolExecutor详解
FutureTask详解