多线程之ReentrantLock
多线程之ReentrantLock
ReentrantLock 与 AQS



//加锁 |
重入锁实现同步过程:
- 线程1调用lock()加锁,判断state=0,所以直接获取到锁,设置state=1 exclusiveOwnerThread=线程1。
- 线程2调用lock()加锁,判断state=1 exclusiveOwnerThread=线程1,锁已经被线程1持有,线程2被封装成节点Node加入同步队列中排队等锁。此时线程1执行同步代码,线程2阻塞等锁。
- 线程1调用unlock()解锁,判断exclusiveOwnerThread=线程1,可以解锁。设置state减1,exclusiveOwnerThread=null。state变为0时,唤醒AQS同步队列中head的后继节点,这里是线程2。
- 线程2被唤醒,再次去抢锁,成功之后执行同步代码。
线程最终获取到锁的标志就是AQS.state>0
且AQS.exclusiveOwnerThread==当前线程
。
SYNC
- NoFairSync
- FairSync
LOCK Interface lock 借助sync
public void lock() { sync.acquire(1); } |
NOFairSync (默认为非公平锁)
非公平锁,如果CAS state 0->1, 则占有锁
public boolean tryAcquire(int acquires) { |
FairSync
公平锁, 如果CAS state 0->1, 并且没有等待该线程的锁,才占有锁
protected final boolean tryAcquire(int acquires) { |
区别
当判断到锁状态字段state == 0 时,不会立马将当前线程设置为该锁的占用线程,而是去判断是在此线程之前是否有其他线程在等待这个锁(执行hasQueuedPredecessors()方法),如果是的话,则该线程会加入到等待队列中,进行排队(FIFO,先进先出的排队形式)
总结
通常非公平锁性能高于公平锁,原因是: 恢复一个被挂起线程与线程执行存在延迟,在竞争激烈的情况下,比如a持有锁,b被挂起,a释放锁时,b将被唤醒并尝试获取锁。于此同时C再b唤醒前已经获得,使用并释放了该锁。。
如果每个线程持有锁的时间都比较长,可以使用公平锁。非公平锁抢占可能导致线程饥饿。
线程排队
public final void acquire(int arg) { |
获锁失败时候,AddWaiter(Node.EXCLUSIVE)就是请求排队的
如何排队?
双向链表实现的队列
关键信息:
- 当前线程
- 线程状态
- 前驱和后继
线程的2种等待模式:
- SHARED:表示线程以共享的模式等待锁(如ReadLock)
- EXCLUSIVE:表示线程以互斥的模式等待锁(如ReentrantLock),互斥就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁
线程在队列中的状态枚举:
- CANCELLED:值为1,表示线程的获锁请求已经“取消”
- SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
- CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足
- PROPAGATE:值为-3,当线程处在“SHARED”模式时,该字段才会被使用上
例子
线程A获锁成功,无排队线程
线程B申请锁,排队
如果感知锁空闲并获取锁
acquireQueued方法就是把放入队列中的这个线程不断进行循环“获锁”,直到它“成功获锁”或者“不再需要锁(如被中断)”
解锁的时候:
unparkSuccessor(h)方法就是“唤醒操作
public void unlock() { |
ReentrantReadWriteLock
ReentrantLock 是互斥锁,每次最多只有一个线程获得。
ReentrantReadWriteLock支持以下功能:
- 支持公平与非公平的获取锁方式。
- 支持可重入,读线程获取读锁后还可以获取读锁,但是不能获取写锁;写线程获取写锁后既可以再次获取写锁还可以获取读锁。
- 允许从写锁降级为读锁,其实现方式是:先获取写锁,然后获取读锁,最后释放写锁。但是,从读锁升级到写锁是不可以的;
- 读取锁和写入锁都支持锁获取期间的中断;
- Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。
读写锁: 一个资源可以允许多个读操作,或者一个写操作,读写不能同时进行。
*/ |
两个锁,当线程对锁的持有时间长并且大部分操作不会修改共享资源,使用读写错
example
class CachedData { |
ReadLock 和 WriteLock 中的方法都是通过 Sync 这个类来实现的。Sync 是 AQS 的子类,然后再派生了公平模式和不公平模式。
锁降级
将持有写锁的线程,去获取读锁的过程称为锁降级(Lock downgrading)。这样,此线程就既持有写锁又持有读锁。
但是,锁升级是不可以的。线程持有读锁的话,在没释放的情况下不能去获取写锁,因为会发生死锁。
锁降级的本质是释放掉独占锁,使其他线程可以获取到读锁,提高并发,而当前线程持有读锁来保证数据的可见性。
ReentrantLock Vs Synchronized Vs Volatile
https://mp.weixin.qq.com/s/-Q-K9zhb8k0c3n_5zhxuTg
volatile 保证可见性,不保证原子性 (例如 i++ 为符合操作)
Volatile实现内存可见性是通过store和load指令完成的;对volatile变量执行写操作时,会在写操作后加入一条store指令,强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。
synchronized关键字能够实现原子性和可见性;synchronized底层是通过使用对象的监视器锁(monitor)来确保同一时刻只有一个线程执行被修饰的方法或者代码块。
Java5以后出现的juc包(java.util.concurent)中有很多Lock的实现类
1、读写锁(ReadLock、WriteLock、ReadWriteLock)
2、可重入锁(ReentrantLock)
3、可中断锁
4、公平锁和非公平锁 (在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。)
etc..
https://zhuanlan.zhihu.com/p/54297968
https://mp.weixin.qq.com/s/QzbrsRAC0WxyBjruqrmZhw
https://mp.weixin.qq.com/s/hump_BZTt7FX8w_Q9LmSnw