CH07
- AQS(AbstractQueuedSynchronized):提供了一种实现阻塞锁和一系列依赖
FIFO等待队列
的同步器的框架, 用于构建锁或者其他同步装置; JUC的核心; - Sync queue: 同步队列, 双向链表, 竞争锁失败的线程被打包成Node加入这个队列
- Condition queue: 单向链表, 只有当使用Condition的时候才会用上, 且同一时刻可能有不止一个Condition queue
- 设计:
- 使用Node实现FIFO队列, 用于构建锁或者其他同步装置
- state(int): 表示获取锁的线程数; volatile保证可见性; 对其操作为原子操作
- 子类通过继承并实现它的方法管理其状态{acquire和release}的方法操纵状态
- 可以同时实现排他锁和共享锁模式(使用者角度: 独占、共享);
- 独占: 只有一个线程能执行, 如ReentrantLock
- 共享: 多个线程可同时执行, 如信号量/CountDownLatch
- AQS同步组件:
- CountDownLatch:
- 用来完成类似阻塞当前线程的功能, 即一个线程或多个线程等待直到其他线程完成操作,
- 使用场景: 程序执行需要等待某个条件完成后才能继续走, 比如说并行计算, 将任务拆成子任务分发, 等待所有请求被处理完后再统计结果
- Semaphore: 和操作系统中的信号量意义是一样的, 提供PV操作, 自己会做好并发控制; 有一个特点是可以在
acquire()
时写进所需资源的数量;tryAcquire()
用来判断当前是否能够P操作, 该函数的重载还可以设置等待的时间 - CyclicBarrier: 允许一组线程相互等待, 知道达到某个公共的屏障点, 即只有当每个线程都准备就绪后才能各自往下执行;
- ReentrantLock(重点): 可重入锁, 可以创建该锁下的多个条件变量Condition
- Condition: 条件变量, 举个例子, 饭堂打饭会有点菜的队伍和取菜的队伍, 点菜和取菜就是打饭窗口这个锁下的两个条件变量
- CountDownLatch:
- CyclicBarrier与CountDownLatch的区别
- 前者的计数器是可以循环使用的, 后者只有一次,减完就没了
- 前者描述多个线程相互等待达到共同屏障点, 后者是一个或多个线程等待其他线程完成某项操作后再继续执行
- CountDownLatch
- CyclicBarrier
- Java中两个锁: synchronized 和 JUC的ReentrantLock
- 前者可以看成是操作系统提供的锁, 后者看成是用户自定义的锁
- 性能: synchronized引入偏向锁、自旋锁后和两者性能差不多
- ReentrantLock更细
- ReentrantLock独有功能:- 可指定是公平锁还是非公平锁
- 提供Condition类, 可以分组唤醒需要唤醒线程(synchronized只有一个等待池)
- 提供能够中断等待锁的线程的机制, lock.lockInterruptibly()
- ReentrantLock是靠自旋锁来实现的, 依赖CAS操作加锁, 避免线程进入内核态的阻塞状态
- 使用读写锁(ReentrantLock)时, 当读线程很多时, 写线程容易遭受饥饿
- StampedLock的乐观读(读锁): StampedLock的状态是由版本和模式组成, 当读操作远多于写操作时, 认为同时出现的可能性低, 可以先读再判断
- 少量竞争者时, 建议用 synchronized; 竞争者不少, 线程增长趋势可预估, 可用 ReentrantLock(常见写代码的错误是忘记释放锁而造成死锁)
- 当使用Condition时, 作用相当于管程中条件变量
CH08
- FutureTask: 可以在线程执行任务后得到执行结果
- Callable与Runnable接口对比: Runnable就一个run函数, Callable是一个范型接口, 范型是call()的参数
- Future接口: 对具体的Runnable或Callable任务, 可以进行取消、查询状态、获取结果; 总之就是可以得到别的线程任务方法的返回值
- FutureTask类: 最终也是执行Callable的任务
- 具体用法: 向 ExecutorService的
submit()
方法 传入一个Callable对象并返回一个Future对象, 调用Future对象的get()
方法, 该线程会阻塞直到拿到Callable对象的返回值。PS: 在用Callable创建完对象并start后就开始运行了, 而不是调用get()才开始 - Future VS FutureTask:
- Future只是一个接口, 无法直接用来创建对象, FutureTask是一个类, 可以用来创建对象
- FutureTask相当于继承了Runnable和Future接口, 所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
- Fork/Join框架: 用于并行执行, 大任务分成多个小任务, 与MapReduce想法相似. 基于工作窃取算法
- BlockingQueue:
- 本质是一个队列, 当一个线程试图对满的队列进行入队操作或者一个线程试图对空队列作出队操作都会被阻塞
- 线程安全的, 该模型的原型是"生产者消费者"
- ArrayBlockingQueue: 有界(初始化时就分配好队列大小)的队列
- DelayQueue: 阻塞内部元素(必须实现Delay接口); 应用场景: 定时关闭连接等
- LinkedBlockingQueue: 如果初始化时有指定大小则有界, 否则默认为最大的整型值
- PriorityBlockQueue: 无界, 有序(队列元素必须实现comparable), 允许插入null
- SynchronousQueue: 队列中只有一个元素(非缓存)
多线程笔记
- 如何理解notify/notifyAll的作用? 首先理解有等待池(线程提出wait之后到这里来)和锁池(没争到锁的线程被阻塞到这里,可以竞争锁), notify/notifyAll可以看作是将等待迟里的线程搬到锁池
- Java常见的几种调用机制(同步调用,异步调用,回调
- Java ExecutorService四种线程池的例子与说明
- 当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。