上文详细介绍了AQS的设计思想,以及总体设计结构。下面我们来介绍一下另一个和锁与AQS相关的接口,Lock接口,然后借用AQS和Lock接口快速实现一个自定义锁。
AQS相关文章:
AQS(AbstractQueuedSynchronizer)源码深度解析(1)—AQS的设计与总体结构
AQS(AbstractQueuedSynchronizer)源码深度解析(2)—Lock接口以及自定义锁的实现
AQS(AbstractQueuedSynchronizer)源码深度解析(3)—同步队列以及独占式获取锁、释放锁的原理【一万字】
AQS(AbstractQueuedSynchronizer)源码深度解析(4)—共享式获取锁、释放锁的原理【一万字】
AQS(AbstractQueuedSynchronizer)源码深度解析(5)—条件队列的等待、通知的实现以及AQS的总结【一万字】
public interface Lock
Lock接口本来和AQS没有太多关系的,但是如果想要是实现一个正规的、通用的同步组件(特别是锁),那就不得不提Lock接口。
Lock接口同样自于JDK1.5,它被描述成JUC中的锁的超级接口,所有的JUC中的锁都会实现Lock接口。
由于它是作为接口,到这里或许大家都明白了它的设计意图,接口就是一种规范。Lcok接口定义了一些抽象方法,用于获取锁、释放锁等,而所有的锁都实现Lock接口,那么它们虽然可能有不同的内部实现,但是开放给外部调用的方法却是一样的,这就是一种规范,无论你怎么实现,你给外界调用的始终是“同一个方法”!因此JUC中的锁也被常常统称为lock锁。
这种优秀的架构设计,不同的锁实现统一了锁获取、释放等常规操作的方法,方便外部人员使用和学习!类似的设计在JDBC数据库驱动上面也能看到!
我们来看看实现Lcok接口都需要实现哪些方法!
方法名称 | 描述 |
lock | 获取锁,如果锁无法获取,那么当前的线程被挂起,直到锁被获取到,不可被中断。 |
lockInterruptibly | 获取锁,如果获取到了锁,那么立即返回,如果获取不到,那么当前线程被挂起,直到当前线程被唤醒或者其他的线程中断了当前的线程。 |
tryLock | 如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false |
tryLcok(long time,TimeUnit unit) | 在指定时间内尝试获取锁。如果获取了锁,那么返回true,如果当前的锁无法获取,那么当前的线程被挂起,直到当前线程获取到了锁或者当前线程被其他线程中断或者指定的等待时间到了。时间到了还没有获取到锁则返回false。 |
unlock | 释放当前线程占用的锁 |
newCondition | 返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁 |
Thread类中有一个interrupt方法,可中断因为主动调用Object.wait()、Thread.join()和Thread.sleep()等方法造成的线程等待,以及使用lockInterruptibly方法和tryLock(time,timeUnit)尝试获取锁但是未获取锁而造成的阻塞,并且他们都将抛出InterruptedException异常,并且设置该线程的中断状态位为false。
但是对于因为调用lock()方法,或者因为无法获取Synchronized锁而被阻塞的线程,interrupt方法无法中断,仅会设置中断标志位为true,另外对于正常线程,同样仅会设置中断标志位为true,通知该线程应该被中断了。有一个特例是:被LockSupport.park()阻塞的线程也可以被中断,但是不会抛出异常,并且不会恢复标志位。
下面举例详细说明Lock接口的这四种方法的使用:假如线程A和线程B使用同一个锁LOCK,此时线程A首先获取到锁LOCK.lock(),并且始终持有不释放。
如果此时B要去获取锁,有四种方式:
经过上面的学习,我们知道了AQS的大概设计思路与方法,以及规范的锁需要实现Lock接口,现在我们尝试自己构建一个简单的独占锁。
顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁,下面是一个基于AQS的独占锁的实现。
我们需要重写tryAcquire和tryRelease(int releases)方法。将同步状态state值为1看锁被获取了,使用setExclusiveOwnerThread方法记录获取到锁的线程,state为0看作锁没被获取。另外,下面的简单实现并没有可重入的考虑,因此不具备重入性!
从实现中能够看出来,有了AQS工具,我们实现自定义同步组件还是比较简单的!
/**
* @author lx
*/
public class ExclusiveLock implements Lock {
/**
* 将AQS的实现组合到锁的实现内部
* 对于同步状态state,这里的实现将1看成同步,0看成未同步
*/
private static class Sync extends AbstractQueuedSynchronizer {
/**
* 重写isHeldExclusively方法
*
* @return 是否处于锁占用状态
*/
@Override
protected boolean isHeldExclusively() {
//state是否等于1
return getState() == 1;
}
/**
* 重写tryAcquire方法,尝试获取锁
* 这里的实现为:当state状态为0的时候可以获取锁
*
* @param acquires 参数,这里我们没用到
* @return 获取成功返回true,失败返回false
*/
@Override
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 重写tryRelease方法,释放锁
* 这里的实现为:当state状态为1的时候,将状态设置为0
*
* @param releases 参数,这里我们没用到
* @return 释放成功返回true,失败返回false
*/
@Override
protected boolean tryRelease(int releases) {
//如果尝试解锁的线程不是加锁的线程,那么抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
//设置当前拥有独占访问权限的线程为null
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 返回一个Condition,每个condition都包含了一个condition队列
* 用于实现线程在指定条件队列上的主动等待和唤醒
*
* @return 每次调用返回一个新的ConditionObject
*/
Condition newCondition() {
return new ConditionObject();
}
}
/**
* 仅需要将操作代理到Sync实例上即可
*/
private final Sync sync = new Sync();
/**
* lock接口的lock方法
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* lock接口的tryLock方法
*/
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* lock接口的unlock方法
*/
@Override
public void unlock() {
sync.release(1);
}
/**
* lock接口的newCondition方法
*/
@Override
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
}
如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!