ReentrantLock(Lock中使用频率最高的类)-可重入锁
内建锁隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。
一、重入性实现原理
重入性锁的特点:线程获取锁的时候,如果已经获取锁的线程是当前线程直接再次获取
由于锁会被获取N次,因此锁只有被释放N次之后才算真正释放成功
如何实现可重入?
以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:
final boolean nonfairTryAcquire(int acquires) {
//拿到当前线程
final Thread current = Thread.currentThread();
//获取当前同步状态
int c = getState();
// 1.如果该锁未被任何线程占有,当前线程使用CAS尝试获取同步状态
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2.此时同步状态不为0,表示已经有线程获取到了同步状态
//判断持有线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
// 3.若是当前线程,同步状态再次+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//将再次+1后的状态写回内存
setState(nextc);
return true;
}
return false;
}
锁的释放:
protected final boolean tryRelease(int releases) {
// 1.同步状态-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 2.只有当同步状态为0时,锁成功释放,返回false
free = true;
setExclusiveOwnerThread(null);
}
// 3.锁未被完全释放,返回false
setState(c);
return free;
}
二、公平锁与非公平锁
公平锁:锁的获取顺序一定满足时间上的绝对顺序,等待时间最长的线程一定最先获取到锁。
非公平锁:等待时间最长的线程不一定先获取到锁
ReentrantLock默认使用非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
可传入一个boolean值,true时为公平锁,false时为非公平锁:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
1.非公平锁nonFairSync:
final void lock() {
//不在队列中的线程可能会直接获取到锁
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//-------------------------------------------------
// 1.如果该锁未被任何线程占有,该锁能被当前线程获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
//------------------------------------------------
}
// 2.若被占有,检查占有线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 3.再次获取,计数+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2.公平锁FairSync:
final void lock(){
//少了一次CAS过程
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
if (c == 0) {
// 增加了!hasQueuedPredecessors()
// 当同步队列中存在非空节点,当前线程直接封装为Node节点排队
if (!hasQueuedPredecessors() &&
//hasQueuedPredecessors() :实现公平锁,先检测队列中是否有非空节点,没有CAS竞争
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
}
3.公平锁与非公平锁对比:
公平锁保证每次获取锁均为同步队列的第一个节点,保证了请求资源时间上的绝对顺序,但是效率较低,需要频繁的进行上下文切换
非公平锁会降低性能开销,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。但是可能导致其他线程永远无法获取到锁,造成线程"饥饿"现象。
通常来讲,没有特定的公平性要求尽量选择非公平锁(ReentrantLock默认选择)
三、ReentrantReadWriteLock(可重入读写锁)
读写者模型:
读写锁允许同一时刻被多个读线程访问,
但是在写线程访问时,所有的读线程以及其他写线程均会被阻塞。
1.写锁详解-独占锁
写锁的获取-tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
// 获取读写锁状态
int c = getState();
// 获取独占式锁状态-即写锁状态
int w = exclusiveCount(c);//当前写锁的获取次数
if (c != 0) {
// 表示当前有读线程拿到读锁,写线程无法获取同步状态
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁的可重入次数已达最大值
if (w + exclusiveCount(acquires) > MAX_COUNT)//左移16位-1(乘2的10次方)
throw new Error("Maximum lock count exceeded");
// 写锁可重入
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 此时读写状态为0,写锁可以正常获取到同步状态
// 将当前线程置为只有写锁线程
setExclusiveOwnerThread(current);
return true;
}
写锁的释放通过重写AQS的tryRelease方法,源码为:
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 同步状态减去写状态
int nextc = getState() - releases;
// 当前写状态是否为0,为0则释放写锁
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
// 不为0则更新同步状态
setState(nextc);
return free;
}
2.读锁-共享锁(一般与独占锁搭配使用实现读写者模型)
读锁获取:
只要当前没有写线程获取到写锁并且读锁的获取次数不超过最大值,读锁就能获取成功。
读锁的释放:减到0彻底将锁释放
读写锁的应用场景:缓存的实现
写锁的降级:写锁可以降级为读锁,但是读锁不能升级为写锁。
注意:读锁 != 无锁:(写锁时所有读线程也停止,读锁可以限制个数)
3.如何区分读状态与写状态:
同步状态的高16位表示读锁获取次数;同步状态低16位表示写锁获取次数。
四、Condition的await与signal 等待/通知机制
Object的wait与notify是与内建锁(对象监视器)搭配使用,完成线程的等待与通知机制。Condition的await、signal是与Lock体系配合实现线程的等待与通知(Java语言层面实现,具有更高的控制与扩展性。)
1.Condition有以下三个独有特性(内建锁不具备)
I.Condition await支持不响应中断,而Object提供wait不支持。(Lock支持响应中断)
II.Condition支持多个等待队列,而Object wait只有一个等待队列。
III.Condition支持设置截止时间,而Object wait只支持设置超时时间。
2.等待方法:await()
I.void await() throws InterruptedException(同wait());当前线程进入等待状态,如果在等待状态中被中断会抛出被中断异常;
II.void awaitUninterruptibly();特性1,等待过程中不响应中断
III.boolean await(long time, TimeUnit unit) throws InterruptedException;在I的基础上增加了超时等待功能,可以自定义时间单位
IV. boolean awaitUntil(Date deadline) throws InterruptedException;特性3,支持设置截止时间
await实现原理:
public final void await() throws InterruptedException {
// 判断中断
if (Thread.interrupted())
throw new InterruptedException();
============// 将当前线程封装为Node入等待队列===============================
Node node = addConditionWaiter();
// .释放当前线程所占用的lock,释放后会唤醒同步队列中的下一个节点
int savedState = fullyRelease(node);
int interruptMode = 0;
//
while (!isOnSyncQueue(node)) {
// 当线程不在同步队列后将其阻塞,置为WAIT状态
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 在同步队列中排队获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
如何将当前线程插入等待队列 addConditionWaiter()
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
// 清空所有等待队列中状态不为Condition的节点
unlinkCancelledWaiters();
// 将最新的尾节点赋值
t = lastWaiter;
}
// 将当前线程包装为Node节点且状态为Condition
Node node =
new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
// 尾插入等待队列
lastWaiter = node;
return node;
}
将线程包装为Node节点尾插入等待队列后,线程释放锁过程fullyRelease()
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 获取当前同步状态
int savedState = getState();
// 调用AQS的释放同步状态方法release()
if (release(savedState)) {
failed = false;
return savedState;//释放成功状态为0
} else {
throw new IllegalMonitorStateException();
}
} finally {
// 若在释放过程中出现异常,将当前节点取消
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
线程如何能从await()方法中退出?
while (!isOnSyncQueue(node)) {
// 阻塞在此处
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
I.在等待时被中断,通过break退出循环
II.被唤醒后置入同步队列,退出循环。
调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中
public final void signal() {
//判断当前节点是否拿到锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 拿到当前等待队列的头结点
Node first = firstWaiter;
if (first != null)
// 唤醒头结点
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 将头结点从等待队列中移除
first.nextWaiter = null;
// transferForSignal方法对头结点做真正的处理
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
// 首先将节点状态更新为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
// 将节点使用enq方法尾插到同步队列中
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
同步队列中的节点置入等待队列:同步队列的头节点作为等待队列的最后一个节点唤醒:唤醒等待队列的头节点,放入同步队列最后一个节点
signalAll()方法:
将等待队列中的每一个节点都移入到同步队列中,即“通知”当前调用condition.await()方法的每一个线程
4.Condition等待队列
Condition队列与AQS中的同步队列共享节点(Node)数据结构,带有头尾指针的单向队列
每当调用lock.newCondition()就会在绑定的lock锁上新增一个等待队列。(特性2)