一、wait/notify
Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间
BLOCKED线程会在Owner线程释放锁时唤醒
WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着重新获得锁,仍须进入EntryList重新竞争
Sleep和Wait的区别
1、sleep是Thread方法,而wait是Object方法
2、sleep不需要强制和synchronized方法使用,而wait需要和sychronized一起使用
3、sleep再睡眠时不会释放对象锁,但wait在等待的时候会释放对象锁
4、他们的状态都是TIMED_WAITING
二、多锁
1、锁的活跃性
死锁、活锁、饥饿
2、Reentrantlock
相对于synchronized特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
3、ReentrantLock特性
- 可重入
- 可打断
- 超时锁
- 公平锁
条件变量
二、共享模型之内存
上节的Monitor是访问共享变量时,保证临界区代码的原子性
本章主要学习共享变量在多线程间的可见性,问题与多条指令执行时的有序性问题
2.1 Java内存模型
JMM定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等
JMM主要体现在:
- 原子性-保证指令不会受到线程上下文切换的影响
- 可见性-保证指令不会受CPU缓存的影响
- 有序性-保证指令不会受CPU指令并行优化的影响
Volatile
轻量级,保证线程指令的可见性,但不能保证原子性
Synchornized
重量级,既能保证线程指令的原子性,又能保证可见性
指令重排序
JVM在不影响正确性的前提下,可以调整语句的执行顺序,这种特性称之为【指令重排】,多线程下指令重排会影响正确性。
现代CPU支持多级指令流水线,CPU可以在一个时钟周期内,同时运行多条指令的不同阶段,流水线技术不能缩短单条指令的执行时间,但变相提高了指令的吞吐率。
Volatile的底层实现原理是内存屏障
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的读指令前会加入读屏障
三、共享模型之无锁
主要内容:
- CAS与volatile
- 原子整数
- 原子引用
- 原子累加器
- Unsafe
3.1原子累加器
源码之LongAdder
关键域:
// 累加单元数组,懒惰初始化
transient volatile Cell[] cells;
// 基础值,如果没有竞争,则cas累加这个域
transient volatile long base;
// 在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;
3.2 Unsafe
是在sun.misc包下的类,不属于java标准,提供了一些相对底层方法来操作系统底层资源,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
四、共享模型之不可变
- 不可变类的使用
- 不可变类设计
- 无状态类设计
五、AQS
全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
特点:
- 用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState-获取state状态
- setState-设置state状态
- compareAndSetState - cas机制设置state状态
- 独占模式是只有一个线程能访问资源,共享模式允许多个线程访问资源
- 提供了基于FIFO的等待队列,类似于Monitor的EntryList
- 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet
获取锁的姿势
// 如果获取锁失败
if (!tryAcquire(arg)) {
// 入队,可以选择阻塞当前线程 park unpark
}
释放锁的姿势
// 如果释放锁成功
if (tryRelease(arg)) {
// 让阻塞线程恢复运行
}
5.1ReentrantLock读写锁
注意事项
- 读锁不支持条件变量
- 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致写锁永久等待
r.lock();
try {
w.lock();
try {
} finally {
w.unlock();
}
} finally {
r.unlock();
}
- 重入时降级支持:即持有写锁的情况下去获取读锁
class CachedData {
Object data;
// 是否有效,如果失效,需重新计算
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData(){
rwl.readLock.lock();
if (!cahceValid) {
// 获取写锁前必须释放读锁
rwl.readLock.unlock();
rwl.writeLock.lock();
try {
//判断是否有其他线程已经获取了写锁、更新了缓存,避免重复更新
if (!cachedValid) {
data = ...
cachedValid = true;
}
//降级为读锁,释放写锁,这样能够让其他线程获取写锁
rwl.readLock.lock();
} finally {
rwl.writeLock.unlock();
}
}
try {
use(data)
} finally {
rwl.readLock.unlock();
}
}
}
六、ReentrantLock
相对于sychronized具备如下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与sychronized一样,都支持可重入
可重入
指同一个线程如果首次获得了这把锁,那么因为他是这把锁的持有者,因此有权利再次获得这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住