最近较忙,趁晚上继续之前的学习,前面了解了Lock与AbstractQueuedSynchronizer(AQS),也了解了并发的一些重要的概念,今天就学习一下最常用的一个实现类ReentrantLock。
ReentrantLock了解
源码:
public class ReentrantLock implements Lock, java.io.Serializable {....}
从这可以看出来,ReentrantLock实现了Lock接口,而前面了解Lock的时候知道,Lock接口提供了获取锁与释放锁的方法,所以作为实现类,ReentrantLock一定也支持这些功能。
Reentrant 这个单词是折返的意思,也就是重入,上一篇学习到重入锁的概念,所以ReentranLock一定实现了重入锁。。
再简单看一下实现:
从截图上可以看到,ReentranLock使用了AQS代理实现了锁的操作,而且不止一个实现,NonfairSync与FairSync,可以明显看出这是非公平同步器和公平同步器,也体现了前一篇学习到的公平锁与非公平锁。
基于以上理解,
1、可以知道锁的操作都是被同步器实现了,应该也提供了一些常用的方法;
2、实现了公平锁、非公平锁、重入锁;
ReentranLock的公平锁与非公平锁
选择使用公平锁或者非公平锁:
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock提供了一个构造方法,通过参数true或则false决定使用公平锁还是非公平锁。
公平锁实现:
源码:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
....
}
获取锁的时候会调用AQS的acquire(1)方法
AQS的acquire(int)实现:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在这个实现中会首先调用tryAcquire(int)方法,这个方法被FairSync 重写了
FairSync 重写的tryAcquire(int):
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
在锁没有被获取的情况下,会首先执行hasQueuedPredecessors()方法,看看这个方法是做什么的;
这个方法是AQS里面的方法:
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
注释上说是用来帮助实现公平锁的方法,在实现返回值是一个判断结果,表达式包含三个小表达式:
– h != t
– (s = h.next) == null
– s.thread != Thread.currentThread()
h是头部元素, tail是尾部元素 s是头元素的下一个元素 ;
– 如果 h == t 说明只有一个元素 h != t 返回false,否则返回true;如果有多个元素 h != t 返回true;
– 如果只有一个元素 (s = h.next) == null ,返回true;如果有多个元素, 返回false;
– 如果是当前线程,s.thread != Thread.currentThread(), 返回false,否则返回true;
根据上面的分析,有一下结果:
返回结果 | 是当前线程 | 不是当前线程 |
---|---|---|
只有一个元素 | false | false |
多个元素 | false | true |
而实现中判断是:
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires))
为了使!hasQueuedPredecessors()判断通过,就必须满足两个情况:
– 只有一个元素
– 有多个元素但是线程是当前的线程(这个不太明白)或者是头元素
这就保证了一定是头元素获取到锁,这也就保证了逻辑上的公平。
ps:
同步器内部等待线程都在阻塞队列中,队列是有序的,最先进入队列的在最前面,后来进入队列的后面,类似与排队,先排队的先处理。
再来对比一下非公平锁的实现:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
可以看到非公平锁里面没有这个判断。
ReentrantLock的使用
用法:
public class LockTest {
public static void main(String[] args) {
int i = 0;
while (i < 20) {
new Thread(new TestRunnable()).start();
i++;
}
}
static class Test {
static ReentrantLock lock = new ReentrantLock(false);
public static int total = 0;
public static void add() {
lock.lock();
try {
total++;
System.out.println(total);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class TestRunnable implements Runnable {
public void run() {
Test.add();
}
}
}
public class LockTest2 {
static ReentrantLock lock = new ReentrantLock(false);
public static void main(String[] args) {
Test test = new Test();
int i = 0;
while (i < 20) { // 加20次
new Thread(new AddRunnable(test)).start();
i++;
}
}
static class Test {
public int total = 0;
public void add() {
lock.lock();
try {
total++;
System.out.println(total);
} finally {
lock.unlock();
}
}
public void reduce() {
lock.lock();
try {
total--;
} finally {
lock.unlock();
}
}
}
static class AddRunnable implements Runnable {
private Test test;
public AddRunnable(Test test) {
this.test = test;
}
public void run() {
test.add();
}
}
}
上面两种写法,第一种对应Synchronized锁类的情况,第二种对应Synchronized锁实例的情况。
总结
如果了解Lock、了解了AQS、了解各种锁的概念,发现再看实现类的时候,很多原来看不懂的内容都能看懂了,总而言之前面额学习还是有效果的。