一、aqs

AQS是AbstractQueuedSynchronizer的缩写, 是一个用来构建锁和同步器的框架 ,是线程安全问题(原子性)的一种解决方案

通过它可以实现很多不同类型的锁,例如ReentrantLock

主要内容:

  • 用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制获取锁和释放锁。

  • getState: 获取

  • setState: 设置

  • compareAndSetState: cas的方式设置state状态

独占模式是只有一个线程可以访问某个资源,共享模式是允许多个线程同时访问资源。

  • 提供了基于FIFO(先入先出)的等待队列,类似于Monitor的EntryList(竞争锁失败的线程在这里阻塞)
  • 支持用条件变量来实现等待唤醒机制,支持多个条件变量,类似Monitor的WaitSet(调wait方法后在这里阻塞)

二、利用aqs自定义一个简单的不可重入锁

//自定义一个简单的锁
@Slf4j
public class MyLock implements Lock {

    //自己定义同步器对象的实现类,
    class MySync extends AbstractQueuedSynchronizer {

        //实现不可重入+独占锁需要实现下边这几个方法

        //尝试获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            //利用cas把state状态从0改成1,如果改成功了就表示加锁成功
            if(compareAndSetState(0,1)) {
                //设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            //加锁失败
            return false;
        }

        //尝试释放锁
        @Override
        protected boolean tryRelease(int arg) {
            //只有owner可以调用解锁,所以不需要使用cas方法
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //判断是否持有独占锁
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        //直接创建条件变量
        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    //创建同步器类的对象
    private MySync sync = new MySync();

    @Override
    //加锁
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

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

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

    //释放锁
    @Override
    public void unlock() {
        sync.release(1);
    }

    //创建条件变量
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    log.info("获取到锁");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    log.info("t2获取到锁");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });

        t1.start();
        t2.start();

    }
}

三、ReentrantLock的基本原理

ReentrantLock是基于aqs实现的,可以先简单看下它的源码结构

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** 这是一个同步器类继承自AQS,ReentrantLock中提供了公平和非公平两种实现 */
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //省略抽象类中具体代码
    }
    
    //同步器类的非公平实现
    static final class NonfairSync extends Sync {
        //省略具体代码
    }
    //同步器类的公平实现
    static final class FairSync extends Sync {
        //省略具体代码
    }
    
    //无参数构造方法创建的是非公平同步器
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //有参数构造方法可以指定是公平还是非公平模式
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

lock方法加锁时最终都会调用到同步器类实现类中的方法。

接下来我们来看下ReentrantLock中加锁的实现原理图示

state属性表示对资源的持有状态(默认值0),head,tail组成了一个双向链表,这个双向链表实现了类似Monitor对象中的

Entrylist的效果,即等待队列,先入先出,获取锁失败的线程都会在这个队列中等待,(修改state属性的值到期望值失败就表示获取锁失败),开始时头节点是一个空节点,

后续节点都是和线程关联的,在这个队列中前一个节点有唤醒后一个节点的任务,每次都是由头节点唤醒它的下一个节点,然后修改头结点为下一个节点。

具体的内容需要看下源码。

四、ReentrantLock源码分析

4.1 加锁/解锁过程

先看下ReentrantLock加锁的代码

public class ReentrantLock implements Lock, java.io.Serializable {
    
    public void lock() {
        //直接调用同步器的lock方法
        sync.lock();
    }
    
    //非公平实现中的lock方法
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            //这个方法尝试用cas操作修改state的状态从0到1,如果成功了就加锁成功,
            //这是aqs中提供的方法
            if (compareAndSetState(0, 1))
                //加锁成功修改owner线程为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //state修改失败时走这里
                //这个acquire是aqs中的一个方法,具体看下边aqs代码的分析
                acquire(1);
        }

        //这个是非公平锁中的tryAcquire实现
        protected final boolean tryAcquire(int acquires) {
            //这个方法定义在静态内部类Sync中
            return nonfairTryAcquire(acquires);
        }
    }
    
    //静态内部类继承aqs
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //tryAcquire的非公平实现
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // state=0尝试修改state,因为并没有去检测当前是否已经有别的线程再等待,所以是
            // 非公平锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    //修改成功表示加锁成功
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //这里是在处理锁重入,state不是0但是当前线程是Owner,表示发生了锁重入,
            //就给state继续++
            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;
        }
        
        // aqs中解锁时调用的tryRelease抽象方法在这里实现
        protected final boolean tryRelease(int releases) {
            //因为涉及到锁重入,所以会递减state的状态
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //只有当state==0时才会真正释放锁,返回true,否则只是锁重入的释放
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    }
    
    //公平锁实现
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        //公平锁中的lock方法
        final void lock() {
            //直接调用aqs中的acquire
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        // aqs中的acquire会调用这个方法,这是公平的实现
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // hasQueuedPredecessors是aqs中的方法,会检查当前等待队列中是否有已经在等待的线程,
                //如果有的话当前线程就不会去竞争锁了
                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;
        }
    }
}

解锁时不管是公平锁还是非公平锁,调用的都是release方法,实现在aqs类中

涉及到的aqs中的源码分析

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //用cas操作修改state的状态
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
    public final void acquire(int arg) {
    	// tryAcquire会再次尝试一次修改state,如果成功了返回true表示加上了锁,后边的就不用执行了
    	// addWaiter是用来创建一个和当前线程绑定的Node节点,
    	// acquireQueued是把这个节点加到等待队列中,再用 LockSupport.park(this)阻塞当前线程
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //如果阻塞等待过程中当前线程有被打断过,selfInterrupt会把打断标记设为true,
            //告诉调用者等待过程中有被打断过。
            selfInterrupt();
    }
    
    //自我打断
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    
    //把当前线程加入到等待队列中的方法
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//获取node节点的前一个节点
                final Node p = node.predecessor();
                //前一个节点如果是头节点就会再次尝试获取锁,获取成功了就修改头节点为当前
                //线程对应的节点,
                //这块其实是阻塞中的线程被唤醒后执行的逻辑,所以aqs中唤醒线程都是从链表的头节点
                //开始唤醒的,链表中前一个节点有唤醒后一个节点的义务,
                //最开始当第一个线程到队列中阻塞时会创建一个空的头结点连在此节点前边,这个空节点
                //就是为了唤醒第一个阻塞的线程。
                //tryAcquire是一个抽象方法,在非公平锁的实现中新加入的线程会和这个才被唤醒的线程
                //一起抢锁,所以叫非公平锁;在公平锁的实现中新加入的线程会检查当前阻塞队列中是否
                //有还在等待的线程,如果有它就会把自己也加入队列而不去抢锁,所以说是公平锁。
                if (p == head && tryAcquire(arg)) {
                	//当一个线程被唤醒获取锁后它就会把自己修改为头节点,
                	//当它释放锁的时候就会把头节点的下一个节点唤醒
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // shouldParkAfterFailedAcquire方法返回true时执行短路与后边的条件,
                // parkAndCheckInterrupt方法就会让当前线程进入阻塞状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //当一个线程因为被打断而退出阻塞时parkAndCheckInterrupt返回true,
                    // 下边这个赋值语句会执行,下次循环返回给调用者acquire的就是true,
                    //调用者acquire收到true就会进行自我打断
                    //如果是别人释放锁唤醒了这个线程,parkAndCheckInterrupt返回的就是false,
                    //这个语句就不会执行。
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    private final boolean parkAndCheckInterrupt() {
    	//用park方法让当前线程阻塞,本质是调用的unsafe对象中的方法
        LockSupport.park(this);
        //返回并清空打断标记,
        //park的线程被打断时会退出阻塞状态
        return Thread.interrupted();
    }
    
    //第一次进这个方法时前一个节点pred.waitStatus还不是Node.SIGNAL(-1),
    //会进到底下else里修改pred.waitStatus为-1,然后下次循环再进入个方法就会返回true,
    // 回到acquireQueued中就会阻塞住当前线程即node对应的线程
    //-1表示需要唤醒后一个节点
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    //公平锁中会用到,检查当前阻塞队列中是否有已经在等待的线程
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    
    //释放锁时调用的release方法
    public final boolean release(int arg) {
    	// tryRelease是个抽象方法,由子类实现,返回false表示当前释放的只是锁重入
        if (tryRelease(arg)) {
        	//获取等待队列头节点,
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//用LockSupport.unpark唤醒头节点的下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
}

4.2 await和signal分析

ReentrantLock中的条件等待和唤醒是通过条件变量实现的,并且支持多个条件变量。

//这是ReentrantLock中获取条件变量的方法,每次都会创建一个新的条件变量,所以是支持多条件变量
public Condition newCondition() {
        return sync.newCondition();
}

await和signal这些方法都是在Condition这个对象中,它是一个接口,在aqs中定义了它的实现类ConditionObject,我们来具体看下

    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        //这两个变量分别代表条件队列的头节点和尾节点,这是一个单向链表,
        //调用await方法的线程就会创建Node节点添加到这个队列中
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
        
        //await方法,如果阻塞过程中被打断,这个方法会抛出异常
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //创建一个Node节点并添加到条件队列中
            Node node = addConditionWaiter();
            //先释放锁,并保存state的状态,为的是将来被唤醒后再重新获取锁时还原state的状态
            //fullyRelease中不管当前锁重入了几次都会把state变成0,这样才能真正释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //让当前线程阻塞
                LockSupport.park(this);
              //在唤醒方法signal中会从头节点开始找到第一个非空的Node节点从条件队列中转移到aqs中的阻塞队列中,添加到阻塞队列尾部。然后唤醒此node中的Thread
                //被唤醒后就会从这里开始继续执行
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //然后执行到这里的acquireQueued再次被park,等待别的线程释放锁时唤醒它
            // 这个acquireQueued就是aqs中的方法
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        
        //唤醒的方法
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //取条件队列的第一个节点,即等待最久的线程执行唤醒
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        
        private void doSignal(Node first) {
            //从first开始找到第一个不是null的节点从条件队列移除并进行唤醒
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //这是把first从条件队列中移除
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
    }

transferForSignal是aqs中的方法

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //唤醒它,然后就会走到await中把条件节点搬到阻塞队列中的逻辑
            LockSupport.unpark(node.thread);
        return true;
    }