后端进阶笔记08: Lock原理与AQS队列初探

285 阅读5分钟

一、自定义锁实现

我们可以先自己尝试简单地实现一下Lock接口,进而理解Lock锁的工作原理。

首先定义2个变量:lockHolderThreadwaitThreadQueue

  • lockHolderThread:使用AtomicReference类,用于记录当前持有锁对象的线程;
  • waitThreadQueue:使用 LinkedBlockingQueue,用于存储由于未获取到锁而被阻塞的线程;

然后继承Lock接口,实现或完成以下方法:

  • boolean tryLock():使用CAS机制,将lockHolderThread`替换为当前线程;
  • boolean tryUnlock():使用CAS机制,将lockHolderThread替换为空;
  • void lock():调用tryLock(),若失败则将当前线程休眠,并加入waitThreadQueue。若成功,则将当前线程移除waitThreadQueue;
  • void unlock():调用tryUnlock(),若成功则依次唤醒waitThreadQueue的所有线程(或只唤醒队列中的第一个线程);
public class Demo321Lock implements Lock {

    AtomicReference<Thread> lockHolderThread = new AtomicReference<>();
    // 阻塞线程队列
    volatile LinkedBlockingQueue<Thread> waitThreadQueue = new LinkedBlockingQueue<>();

    @Override
    public void lock() {
        boolean isCached = false;
        // 如果CAS获取锁失败,则阻塞并将当前线程加入阻塞队列
        while(!tryLock()){
            if(!isCached){
                waitThreadQueue.offer(Thread.currentThread());
                isCached = true;
            }else{
                LockSupport.park();
            }
        }
        waitThreadQueue.remove(Thread.currentThread());
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {}

    @Override
    public boolean tryLock() {
        // CAS机制尝试获取锁
       return lockHolderThread.compareAndSet(null,Thread.currentThread());
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public boolean tryUnlock(){
        return lockHolderThread.compareAndSet(Thread.currentThread(),null);
    }

    @Override
    public void unlock() {
        // CAS机制释放锁,并唤醒其他线程
        if(tryUnlock()){
            // 唤醒全部线程
            /*for (Thread thread : waitThreadQueue) {
                LockSupport.unpark(waitThreadQueue.iterator());
            }*/
            // 唤醒队列中的第一个线程
            if(waitThreadQueue.iterator().hasNext()){
                LockSupport.unpark(waitThreadQueue.iterator().next());
            }
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

自定义锁使用演示:可以看到基本实现了线程安全

public class Demo321 {

    private static Lock lock = new Demo321Lock();

    private static int count ;

    public static void main(String[] args) throws InterruptedException {
        new Thread(Demo321::incr).start();
        new Thread(Demo321::incr).start();
        new Thread(Demo321::incr).start();
        new Thread(Demo321::incr).start();
        Thread.sleep(5000);
        System.err.println(count);
    }

    private static void incr(){
        for (int i = 0; i < 100; i++) {
            try {
                lock.lock();
                Thread.sleep(10);
                count++ ;
            }catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

二、抽象自定义Lock(模拟AQS)

基于上述自定义锁实现,现在再做一层封装,把lock()unlock()的逻辑再抽取一层出来:

public abstract class Demo322Aqs {
    // 3个核心元素:重入状态、持有锁的线程、阻塞线程队列
    // 2+2个核心方法:tryLock、tryUnlock与lock、unlock

    // 统计当前线程的加锁次数(可重入)
//    volatile AtomicInteger state = new AtomicInteger(0);
    // 持有锁的线程
    volatile AtomicReference<Thread> lockHolderThread = new AtomicReference<>();
    // 阻塞线程队列
    volatile LinkedBlockingQueue<Thread> waitThreadQueue = new LinkedBlockingQueue<>();

		// 具体的加锁、解锁交由具体的锁对象实现,这里只做抽象的定义
    public abstract boolean tryLock();
    public abstract boolean tryUnlock();

    /**
     * 加锁
     */
    public void lock(){
        boolean isCached = false;
        // 如果CAS获取锁失败,则阻塞并将当前线程加入阻塞队列
        while(!tryLock()){
            if(!isCached){
                waitThreadQueue.offer(Thread.currentThread());
                isCached = true;
            }else{
                LockSupport.park();
            }
        }
        waitThreadQueue.remove(Thread.currentThread());
    }

    /**
     * 释放锁
     */
    public void unlock(){
        // CAS机制释放锁,并唤醒其他线程
        if(tryUnlock()){
            for (Thread thread : waitThreadQueue) {
                LockSupport.unpark(thread);
            }
        }
    }

}

锁的实现就可以做如下精简:

public class Demo322Lock implements Lock {

    private volatile MyAqs sync = new MyAqs();

    @Override
    public void lock() {
        sync.lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return sync.tryLock();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public boolean tryUnlock(){
        return sync.tryUnlock();
    }

    @Override
    public void unlock() {
        sync.unlock();
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    static class MyAqs extends Demo322Aqs {

        @Override
        public boolean tryLock() {
            return  lockHolderThread.compareAndSet(null,Thread.currentThread());
        }

        @Override
        public boolean tryUnlock() {
            return  lockHolderThread.compareAndSet(Thread.currentThread(),null);
        }
    }
}

最终的锁使用与之前未封装时的使用无异,代码略。

在真正引入AQS这个概念之前,首先做如下理解上的定义:

  • lock()unLock():其实不是真正的加锁、解锁操作,只是调用资源、释放资源的操作。
  • tryLock()tryUnlock():是真正给线程加锁、阻塞线程、给线程解锁的操作。

三、AQS(AbstractQueuedSynchronizer:抽象队列同步器)

至此,我们在不知不觉中,已经模拟了一个简易的AQS,真正的AQS就是自己实现了lock()unLock()的方法(在AQS中对应的方法名是:acquire()release()),而tryLock()tryUnlock()(在AQS中对应的方法名是:tryAcquire()tryRelease())。这是因为各种不同的锁对象,其获取锁、释放锁时需要执行的操作不尽相同,然而加锁时的锁定资源(阻塞其余线程)、解锁时的释放资源的操作(唤醒其他线程)都是一样的,所以就有了对应的2个abstract方法、2个final方法。

对应到ReentrantLock,内部就定义了一个Sync的静态抽象类提供了tryRelease()方法,而其2个实现类FairSyncNonfairSync则都提供了tryAcquire()方法,其UML图如下:

20200317222957

顺便可以看到,我们可以通过如下方式去控制开启公平锁还是非公平锁,默认启用的是非公平锁

public ReentrantLock() {
  sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

NonfairSynctryAcquire()方法是直接调用了Sycn类中的nonfairTryAcquire()方法,而NonfairSync则是在nonfairTryAcquire()方法的基础上加了一个!hasQueuedPredecessors()(是否队列前面还有线程,以保证先进先出)。值得注意的是,不管在初始化ReentrantLock是否启用了公平锁,在调用ReentrantLock对应的tryLock方法时,都是非公平地去尝试获取锁(tryLock直接调用的Sync对象的nonfairTryAcquire()方法):

public class ReentrantLock implements Lock, java.io.Serializable {
  ……
	abstract static class Sync extends AbstractQueuedSynchronizer {
          ……
          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;
          }
          ……
      }

      static final class NonfairSync extends Sync {
          ……
          protected final boolean tryAcquire(int acquires) {
              return nonfairTryAcquire(acquires);
          }
      }
     static final class FairSync extends Sync {
            ……
            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;
            }
            ……
        }
  }
  public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
  }
	……
}

至于state字段,则用于统计加锁、解锁的次数,每次加锁则state+1,解锁则state-1,只有当state=0时,则将AQS中的exclusiveOwnerThread置为null(彻底释放锁:没有线程持有当前锁对象)这样就实现了锁重入。

最后在说下这里为什么会要执行selfIterrupt()方法:

20200317231013