手写Lock锁
锁的实现需要:
两个方法:lock()、unlock()
两个组件:锁的持有者、等待队列
代码:
public class CustomLock implements Lock{
//当前前程
AtomicReference<Thread> current = new AtomicReference();
//等待队列
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue();
@Override
public void lock(){
//先放入等待队列
waiters.add(Thread.currentThread());
//循环争抢锁
while(!current.compareAndSet(null, Thread.currentThread())) {
//add与remove放在循环里面会出现未抢到锁,加入等待队列之前,
//此时争抢到锁的线程刚好释放锁,并且唤醒等待队列,则会导致无法被唤醒,永久等待
//waiters.add(Thread.currentThread());
LockSupport.park();
//waiters.remove(Thread.currentThread());
}
//抢到锁后,从等待队列移除当前线程
waiters.remove(Thread.currentThread());
}
@Override
public void unlock() {
//释放锁
if (current.compareAndSet(Thread.currentThread(), null)) {
//唤醒所有等待线程
for (Thread thread: waiters) {
LockSupport.unpark(thread);
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
AQS抽象队列同步器
编写正确的程序并不容易,而编写正常的并发程序就更难了。相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的。手写Lock锁也可能会因为代码编写不正确,而导致不正常的预期,抽象队列同步器AQS是实现同步的基础组件,如果你看过J.U.C并发包的源码,就会发现许多同步工具类底层实现都用到了AQS,CountDownLatch中实现了内部类Sync就是继承AbstractQueuedSynchronizer,ReentrantLock、Semaphore等都是如此。
AQS的结构如图:
AQS核心思想是,如果被请求的资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将资源设置为锁定状态。如果被请求的资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS的所有子类中,要么使用了它的独占锁,要么使用了它的共享锁,不会同时使用它的两个锁。
- acquire、acquireShared:定义了资源争用的逻辑,如果没有抢到,则等待
- tryAcquire、tryAcquireShared:定义了实际执行占用资源的逻辑,有使用者具体实现
- release、releaseShared:定义了释放资源的逻辑,并通知后续节点
- tryRelease、tryReleaseShared:定义了实际释放资源的操作,由使用者具体实现
因此独占锁需要实现的是 tryAcquire(int)、tryRelease(int)
共享锁需要实现的是 tryAcquireShared(int)、tryReleaseShared(int)
基于上述思想和手写的lock我们可以实现一个自己的简易版AQS:
public abstract class CustomAQS{
//独占资源
AtomicReference<Thread> current = new AtomicReference();
//共享资源
AtomicInteger state = null;
//等待队列
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue();
public boolean tryAcquire() {
throw new UnsupportedOperationException();
}
public boolean tryRelease(){
throw new UnsupportedOperationException();
}
public boolean tryAcquireShared(){
throw new UnsupportedOperationException();
};
public boolean tryReleaseShared(){
throw new UnsupportedOperationException();
};
public void acquire(){
waiters.add(Thread.currentThread());
while(!tryAcquire()) {
LockSupport.park();
}
waiters.remove(Thread.currentThread());
}
public void release() {
if (tryRelease()) {
for (Thread thread: waiters) {
LockSupport.unpark(thread);
}
}
}
public void acquireShared(){
waiters.add(Thread.currentThread());
while(!tryAcquireShared()) {
LockSupport.park();
}
waiters.remove(Thread.currentThread());
}
public void releaseShared() {
if (tryReleaseShared()) {
for (Thread thread: waiters) {
LockSupport.unpark(thread);
}
}
}
}
使用我们自己的AQS,我们可以实现Lock锁、CountDownLatch等,
CountDownLatch实现:
public class CustomCountDownLatch extends CustomAQS{
public CustomCountDownLatch(int num) {
state = new AtomicInteger(num);
}
@Override
public boolean tryAcquireShared() {
return state.get() <= 0;
}
@Override
public boolean tryReleaseShared() {
return state.decrementAndGet() <= 0;
}
public void await() {
acquireShared();
}
public void countDown() {
releaseShared();
}
}