Java并发编程--锁原理之独占锁ReentrantLock
程序员文章站
2022-05-04 20:49:06
...
文章目录
独占锁ReentrantLock的原理
(1). 结构
ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取这个锁,其他线程尝试获取就会被阻塞并放入AQS阻塞队列中,类图结构如下
底层是使用AQS来实现的,根据参数来决定其内部是否公平内部类sync是NonfairSync类型的则非公平,如果是FairSync则公平
(2). 获取锁
1). void lock()方法
public void lock() {
sync.lock();
}
// 非公平锁
final void lock() {
if (compareAndSetState(0, 1))// 如果当前锁是*的,就直接获取,这就是竞争锁
setExclusiveOwnerThread(Thread.currentThread());
else// 如果锁被占有,那么去排队
acquire(1);
}
// 公平锁
final void lock() {// 公平锁,就要排队
acquire(1);
}
// 通用
public final void acquire(int arg) {// 同之前AQS
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 非公平锁
// 非公平性体现在如果一个线程释放了锁,在进行下一步唤醒队列中的节点之前别的线程直接竞争到了锁,那么队列中的节点会唤醒失败
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//先获取线程和锁的状态
final Thread current = Thread.currentThread();
int c = getState();
// 这整个if-elseif都可以让没有进行排队直接获取锁的线程直接拿到锁
if (c == 0) {// 如果锁空闲
if (compareAndSetState(0, acquires)) {// 获取到锁
setExclusiveOwnerThread(current);
return true;
}
}else if (current == getExclusiveOwnerThread()) {//如果锁不空闲,但是是自己拿到的
// 锁重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 锁不空闲而且不属于自己,就只能是别人的锁,尝试获取失败
return false;
}
// 公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 不同点就在这,hasQueuedPredecessors()方法中当前线程节点有前继节点返回true,AQS队列为空或当前线程是AQS的第一个节点返回false
// 简单点说就是确保当前节点是头结点后的第一个节点,也就是排到队首的节点,保证只有队首节点才能被唤醒
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前节点已经获取了锁,直接重入,这就不牵扯公平性了
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁当锁*时,就可以直接获取锁,而公平锁需要去acquire()方法内部判断.
两种锁调用的acquire()方法都一样.
tryAcquire()方法有所区别,实现逻辑的区别在于公平锁在锁空闲的情况下,获取锁之前确保了当前节点是头结点后的第一个节点,从而让唤醒的节点一定是队列中排过队的.
2). void lockInterruptibly()方法
对中断响应的获取锁.逻辑都类似,调用的是AQS中可被中断的获取锁方法
3). boolean tryLock()方法
尝试获取锁,如果没获取到不会阻塞
public boolean tryLock() {
// 之前介绍过nonfairTryzaiAcquire(),就是单纯的获取锁,之前的阻塞的逻辑是在acquire中
return sync.nonfairTryAcquire(1);
}
4). boolean tryLock(long timeout, TimeUnit unit)方法
设置了时间,如果在该时间内没有获取到锁,返回false.
TimeUnit参数为时间粒度,直接new一个TimeUnit对象即可.
(3). 释放锁
1). void unlock()方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 逻辑同AQS
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// c为本次重入成功后,锁的重入次数
int c = getState() - releases;
// 如果不是当前线程拿到锁,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果这次重入之后,重入次数为零,证明锁没有被任何线程重入,清空锁持有线程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 设置重入次数
setState(c);
return free;
}
最后用一幅图加深理解
上一篇: 生活里风趣雷人俏皮话
下一篇: 被卖了还帮人家数钱
推荐阅读
-
Java并发编程之显式锁机制详解
-
Java并发之嵌套管程锁死详解
-
Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁
-
详解java并发之重入锁-ReentrantLock
-
Java 重入锁 ReentrantLock 原理分析 javaReentrantLock
-
(转)Java并发之AQS详解 博客分类: java java锁ReentrantLock重入锁AQSAbstractQueuedSynchronized
-
详解Java多线程编程中互斥锁ReentrantLock类的用法
-
详解Java多线程编程中互斥锁ReentrantLock类的用法
-
Java中的显示锁ReentrantLock使用与原理详解
-
Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁