一、ReentrantLock
ReentrantLock是可重入的独立锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被加入该锁的AQS阻塞队列里面。
可以实现公平锁和非公平锁
二、源码分析
2.1 继承关系
实现了lock接口和序列化接口
还有3个静态内部类
其中的关系如下:
其中Sync的lock方法为抽象方法,交给子类去实现
2.2 构造方法
无参构造方法
public ReentrantLock() {
//执行锁为非公平锁
sync = new NonfairSync();
}
带布尔值参数的构造方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.3 常用方法
2.3.1 公平锁的lock方法
先分析非公平锁吧
public void lock() {
//交给Sync的子类实现
sync.lock();
}
非公平锁的lock实现
final void lock() {
//使用CAS方式获取锁
if (compareAndSetState(0, 1))
//获取成功设置当前线程名
setExclusiveOwnerThread(Thread.currentThread());
else
//获取锁失败,去等待队列
acquire(1);
}
加入等待队列,其实就是之前讲的AQS里的方法,具体就不相信说了。只说一下子类实现的tryAcquire方法
public final void acquire(int arg) {
//如果tryAcquire返回false,则执行&&右边语句,线程加入阻塞队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法在AQS是抽象的,由子类执行实现
//尝试获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平方式尝试获取
final boolean nonfairTryAcquire(int acquires) {
//获得当前线程名
final Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
//等于0代表锁已释放
if (c == 0) {
//尝试获取锁
if (compareAndSetState(0, acquires)) {
//成功则设置线程名
setExclusiveOwnerThread(current);
//返回true代表成功
return true;
}
}
//如果线程名和当前使用锁的线程相等,则重入锁
else if (current == getExclusiveOwnerThread()) {
//c + 1
int nextc = c + acquires;
//如果+1后为负数,溢出了,抛异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//设置锁的state为nextc
setState(nextc);
//返回成功
return true;
}
//获取锁失败
return false;
}
以上是非公平锁的lock方法,可以看出,在lock()方法后,马上争夺所资源,在进入队列前的tryAcquire方法中再次尝试争夺所资源,而入队后还要再抢夺一次才挂起。
入队列挂起前争夺了两次锁
2.3.2 公平锁的lock方法
再来看一下公平锁,构造方法参数为true即为公平锁
final void lock() {
acquire(1);
}
//直接进入了这里,看一下tryAcquire方法
public final void acquire(int arg) {
//tryAcquire失败则进去阻塞队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//这里同上
final Thread current = Thread.currentThread();
int c = getState();
//如果锁已经被释放
if (c == 0) {
//判断有无前驱节点
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;
}
}
可以看出非公平锁抢夺比较激烈,而公平锁则是lock方法并不争夺锁,而在tryAcquire方法中判断即使锁已经是释放的,也要先判断阻塞队列中有无节点,如果队列中有节点,则入队等待
2.3.3 释放锁
public void unlock() {
sync.release(1);
}
//使用的是AbstractQueuedSynchronizer里的release方法
public final boolean release(int arg) {
//tryRelease是子类自己实现的,看下这个方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//Sync抽象类实现了此方法,传入的releases为1
protected final boolean tryRelease(int releases) {
//锁的state-1
int c = getState() - releases;
//如果线程名和锁的线程名不一致,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//锁释放标志
boolean free = false;
//只有c等于0锁才释放
if (c == 0) {
free = true;
//释放锁后设置线程名为null
setExclusiveOwnerThread(null);
}
//未释放锁,设置c的大小
setState(c);
//返回锁是否释放成功
return free;
}
释放锁就比较简单了
三、总结
之前没看源码,才看才发现非公平锁和公平锁和之前想的不太一样。
公平锁只在没有队列节点的情况下才直接拿锁,否则入队
非公平锁,在lock后随即抢锁,失败后。在入队前再次抢夺锁资源,入队后挂起前再次抢夺一次,还是失败就挂起。
但是在阻塞对立中等待的节点,非公平锁就没办法了,只能按照队列的顺序依次唤醒
以上是对ReentrantLock的理解,分析的是最基本的方法。如果后面有空就在分析下别的方法。
下面是一个ReentrantLock的生产者消费者模型
package com.cf.test.lock;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
LinkedList<String> list = new LinkedList<>();
int listSize = 10;
new Thread(() -> {
lock.lock();
try {
while (true) {
while(list.size() == listSize){
notEmpty.await();
}
list.add("element");
System.out.println("加入了元素,现在队列大小是:" + list.size());
notFull.signalAll();
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "生产者").start();
new Thread(() -> {
lock.lock();
try {
while (true) {
while(list.size() == 0){
notFull.await();
}
String s = list.poll();
System.out.println("取出了元素:" + s);
notEmpty.signalAll();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "消费者").start();
}
}