之前笔者解析了AQS的源码,在JUC中有很多锁是基于AQS实现的,今天想写个简单的ReentrantLock实现,代码也基本是在看了ReentrantLock源码后写出来的,做个笔记。
总结一下AQS的原理,就是使用一个int类型来表示可申请的锁资源,提供了一系列的原子操作,以及用于放置申请锁的线程的等待队列。实际上定义了一整套完整的多线程访问共享资源的同步框架,具体的解析可以看我的另一篇文章
首先来看ReentrantLock的非公平锁实现,它的类定义:
public class UnfairReentrantLockImpl extends AbstractQueuedSynchronizer implements Lock, Serializable
非公平锁实现了Lock接口,因此需要实现以下的方法:
基本上只要了解接口的语义,基于AQS提供的接口,可以快速实现Lock的接口功能,代码如下,可以看到AQS提供的是线程同步部分的实现:
public void lock() {
//cas的方式修改state值,如果成功,即获得锁
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
} else {
//否则加入等待队列,自旋方式申请锁
acquire(1);
}
}
@Override
public void unlock() {
//释放锁
release(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
//阻塞方式申请锁,指定时间后无论是否申请成功都返回
return tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public boolean tryLock() {
//非阻塞方式申请锁
return nofairTryAcquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
//可中断式的申请锁
acquireInterruptibly(1);
}
@Override
public Condition newCondition() {
return new ConditionObject();
}
//尝试获取锁
private final boolean nofairTryAcquire(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()) {
//如果当前线程已经持有锁,只修改一下state的值即返回,
//体现了可重入锁的特点
int nextC = c + acquires;
if (nextC < 0) {
throw new Error("Maximum lock lcount exceeded");
}
setState(nextC);
return true;
}
return false;
}
ReentrantLock锁继承AQS,必须要实现AQS的两个方法:tryAcquire和tryRelease,这两个方法在AQS只提供了一个抛出异常的实现。tryAcquire的语义是非阻塞式的申请锁,而tryRelease的语义是释放锁,并恢复state的值。在AQS内部,会使用这两个方法构造整个同步器的实现。
//AbstractQueuedSynchronizer定义的方法,该方法在AbstractQueuedSynchronizer中只有一个抛出异常的默认实现
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextC = c + arg;
if (nextC < 0) {
throw new Error("Maximum lock lcount exceeded");
}
setState(nextC);
return true;
}
return false;
}
//AbstractQueuedSynchronizer定义的方法,该方法在AbstractQueuedSynchronizer中只有一个抛出异常的默认实现
@Override
protected boolean tryRelease(int arg) {
int c = getState() - arg;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
ReentrantLock公平锁的实现
ReentrantLock公平锁强调根据线程等待时间长短来分配锁,等待时间最长的获取锁,在实现上,会让等待队列头部的线程获得锁。
公平锁的代码实现与非公平锁基本一致,主要区别在以下两个方法上,公平锁的lock方法会将申请锁的线程直接加入等待队列,根据等待时间排序来决定获取锁的线程。公平锁的tryAcquire方法在判断一个线程能否获得锁时,会比非公平锁多加入一个请求线程是否为头线程的判断,只有头线程能获得线程。读者可以与非公平锁的这两个方法实现进行对比,即可清楚比较出两者的区别
@Override
public void lock() {
//与非公平锁的区别:直接将当前申请锁请求加入队列
acquire(1);
}
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//只有在当前线程为头结点时,才能去获得锁
if (!hasQueuedPredecessors() && compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextC = c + arg;
if (nextC < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextC);
return true;
}
return false;
}
最后,给出一个简单的测试类,这个测试类创建了10个银行账户,初始金额都为1000元,然后有30个管理线程,会随机挑选两个账户,转账一个10以内的随机金额,每个线程独立转账10次。完成所有的转账操作后,计算所有账户总金额,如果总金额与原来一致,证明整个转账过程没有同步错误。
public class LockTest {
private static Account[] accounts = new Account[10];
private static AccountManager[] threads = new AccountManager[30];
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
accounts[i] = new Account(1000);
}
double sum = 0;
for (int i = 0; i < 10; i++) {
sum += accounts[i].getMoney();
}
System.out.println("src sum:" + sum);
for (int i = 0; i < 30; i++) {
threads[i] = new AccountManager();
threads[i].start();
}
for (int i = 0; i < 30; i++) {
threads[i].join();
}
sum = 0;
for (int i = 0; i < 10; i++) {
sum += accounts[i].getMoney();
}
System.out.println("after operation sum:" + sum);
for (int i = 0; i < accounts.length; i++) {
System.out.println("acount-" + i + " res money: " + accounts[i].getMoney());
}
}
private static class AccountManager extends Thread {
@Override
public void run() {
int index = 10;
int fromAccount = 0, toAccount = 0;
while (index > 0) {
fromAccount = (int) (Math.random() * 10);
toAccount = (int) (Math.random() * 10);
if (toAccount == fromAccount) {
toAccount = (toAccount + 1) % 10;
}
try {
AccountMgr.transfer(accounts[fromAccount], accounts[toAccount], Math.random() * 10);
} catch (Exception e) {
e.printStackTrace();
}
index--;
System.out.println(Thread.currentThread() + " complete " + (10 - index) + " transformation from " + fromAccount + " to " + toAccount);
}
}
}
}
public class Account {
private Lock lock = new FairReentrantLockImpl();
private volatile double money;
public Account(final double money) {
this.money = money;
}
public void add(double money) {
lock.lock();
try {
this.money += money;
} finally {
lock.unlock();
}
}
public void reduce(double money) {
lock.lock();
try {
this.money -= money;
} finally {
lock.unlock();
}
}
public double getMoney() {
return money;
}
void lock() {
lock.lock();
}
void unLock() {
lock.unlock();
}
boolean tryLock() {
return lock.tryLock();
}
}
public class AccountMgr {
public static boolean tryTransfer(Account from, Account to, Double money) throws NoEnoughMoneyException {
if (from.tryLock()) {
try {
if (to.tryLock()) {
try {
if (from.getMoney() >= money) {
from.reduce(money);
to.add(money);
} else {
System.out.println("operation failed ");
// throw new NoEnoughMoneyException();
}
return true;
} finally {
to.unLock();
}
}
} finally {
from.unLock();
}
}
return false;
}
public static void transfer(Account from, Account to, Double money) throws NoEnoughMoneyException {
boolean success = false;
do {
success = tryTransfer(from, to, money);
if (!success)
Thread.yield();
} while (!success);
}
public static class NoEnoughMoneyException extends Exception {
}
}