自定义实现互斥锁
我们用过ReentrantLock,想了解ReentrantLock如何实现的吗?如何自己实现自定义锁呢?在此之前我们先了解一些基本的知识。
实现自定义锁,我们先要实现Lock接口
Lock
Lock接口提供的synchronized关键字不具备的主要特性:
Lock是一个接口,它定义了锁获取和释放的基本操作:
AbstractQueuedSynchronizer
队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
同步器可重写的方法与描述:
实现自定义同步组件时,将会调用同步器提供的模板方法,同步器提供的模板方法如下图所示:
只有掌握了同步器的工作原理才能更加深入地理解并发包中其他的并发组件,所以下面通过一个独占锁的示例来深入了解一下同步器的工作原理。
顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁。
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
//当前状态是0的时候获取锁
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁,将状态设置为0
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一个Condition,每个Condition都包含一个Condition队列
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
public void unlock() {
sync.release(0);
}
public Condition newCondition() {
return sync.newCondition();
}
}
接下来我们看看使用效果:
public class LockTest {
private static int sum = 0;
private static Lock lock = new Mutex();
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
Thread thread1 = new Thread(LockTest::add, "thread1");
Thread thread2 = new Thread(LockTest::add, "thread2");
Thread thread3 = new Thread(LockTest::add, "thread3");
Thread thread4 = new Thread(LockTest::add, "thread4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
long endTime = System.currentTimeMillis();
System.out.println(sum);
System.out.println(endTime - startTime);
}
public static void add() {
lock.lock();
try {
System.out.println(Thread.currentThread());
for (int i = 0; i < 100000; i++) {
sum++;
}
} finally {
lock.unlock();
}
}
}
执行结果:
Thread[thread3,5,main]
Thread[thread2,5,main]
Thread[thread1,5,main]
Thread[thread4,5,main]
400000
42
创建四个线程,调用add方法,对sum变进行累加,四个线程累加100000次,最终总结果是:$100000 * 4 = 400000$
下一节我们讨论AbstractQueuedSynchronizer(队列同步器)的实现原理