基于ReentrantLock深入理解AQS源码(二)

示例代码

/**
 * @ClassName AQSDemo
 * @Description TODO
 * @Author Oneby
 * @Date 2021/1/21 11:08
 * @Version 1.0
 */
public class AQSDemo {
    public static void main(String[] args) {

        ReentrantLock lock = new ReentrantLock();

        // 带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制
        // 3个线程模拟3个来银行网点,受理窗口办理业务的顾客
        // A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----A thread come in");
                try {
                    TimeUnit.MINUTES.sleep(20);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "A").start();

        // 第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
        // 进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----B thread come in");
            } finally {
                lock.unlock();
            }
        }, "B").start();

        // 第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
        // 进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----C thread come in");
            } finally {
                lock.unlock();
            }
        }, "C").start();
    }
}

1.new ReentrantLock()

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

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

如果使用默认构造方法,创建的是非公平锁;
如果参数为false,创建的是非公平锁;
如果参数为true,,创建的是公平锁;

2.ReentrantLock.lock()

  final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquir##e(1);
        }

第一次执行lock()方法,state变量值为0,表示lock没有被占用,使用CAS将state变量值变为1,占有锁;
如果在执行lock方法时、其他拥有锁的线程刚好释放锁或者锁处于空闲状态、则直接通过CAS修改state变量、表示占有锁;
非公平锁lock()方法上来就尝试抢占一次锁、而非公平锁不会。

2.1 compareAndSetState(0, 1)
 protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

其实就是调用Unsafe类compareAndSwapInt方法,判断state变量的值是否为0,如果为0,就改为1。如果state不为0,则返回false,表示CAS修改失败;

在这里第一个线程A进来后,就将state改为了1,返回true,并调用 setExclusiveOwnerThread(Thread.currentThread()),将占有锁的线程改为当前线程;

线程B,C进行CAS修改state值,由于state为1,所以CAS修改失败,返回false,进入acquire();

3. acquire()

AbstractQueuedSynchronizer.acquire()

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这个方法包含了AQS获取锁、将获取不到锁的线程入同步队队的流程;

4. tryAcquire(arg)

默认arg的值为1;
AbstractQueuedSynchronizer.tryAcquired(arg)

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

这里使用了模板设计方法,如果子类不重写这个方法,调用这个方法时,就会抛出异常;
NoFairLock.tryAcquire(arg)

 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

非公平锁的tryAcquire()最后会调用nonfairTryAcquire方法;

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取当前线程
            int c = getState();
            //获取state的值;
            if (c == 0) {
            //如果占有锁的线程在此时释放了锁、那么state的值为0,其他线程可以尝试占有锁;(不可能一直处于空闲,否则前面lock方法就占有锁了)
            //对应代码的情况就是:线程A在此刻运行结束、而线程B、C同时执行这个方法、通过CAS尝试去占有锁、将state变为1;
                if (compareAndSetState(0, acquires)) {
                    //通过CAS去修改state的状态,因为可能会有多个线程同时修改。
                    setExclusiveOwnerThread(current);
                    //设置当前线程为占有锁的线程;
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
            //判断当前线程是否是持有锁的线程,如果是,则表示为可重入锁,将state的值+1;
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //state的不为0,且占有锁的线程不是当当前线程,则直接返回false;
            return false;
        }
  1. nonfairTryAcquire方法里,上来先判断一下state的值,为0,则抢占锁;
  2. 如果state不为0, 就判断当前线程是否是持有锁的线程,如果是,则表示为可重入锁,将state的值+1;
  3. state的不为0,且占有锁的线程不是当当前线程,则直接返回false;

回到acquire()方法里,执行下一步;
在这里插入图片描述
即addWaiter(Node.EXCLUSIVE)

5. addWaiter(Thread)

这个方法将获取不到锁的线程封装为Node结点,装入队列;

  private Node addWaiter(Node mode) {
        //1、 将当前结点封装为一个Node结点
        //mode为独占式Exclusive模式;
        Node node = new Node(Thread.currentThread(), mode);
        
        //获得同步队列的尾结点tail;
        //如果已经有队列中已经有Node结点了,则tail不为空;
        //由于我们线程A获得锁、其他线程里的第一个线程运行到这里的时,同步队列是空的,所以tail为null; 先执行enq方法; 
        Node pred = tail;
        //pred指向tail指针指向的结点。
        if (pred != null) {
        //如果tail尾指针不为空,则将当前结点的前向指针指向尾指针所指向的结点。
            node.prev = pred;
            
            if (compareAndSetTail(pred, node)) {
                //再通过CAS将尾指针指向当前结点。
                //将结点的前一个结点的next指针指向当前结点。
                pred.next = node;
                return node;
            }
        }
        //如果此时CLH队列里面没有结点、即head=null,tail=null,就会执行enq创建傀儡结点,这个方法可能会有多个线程同时进入。
        enq(node);
        //返回当前结点。
        return node;
    }
5.1 enq(Node node)

可能会有多个线程进入这个方法,所以需要自旋和CAS即自旋锁来保证原子性;

    private Node enq(final Node node) {
        //自旋操作,可能多个线程同时进来
        for (;;) {
        //先获得队列尾结点tail;
            Node t = tail;
            //刚开始tail为空,判断成立
            if (t == null) { // Must initialize
            //多个线程可能同时进来,只有一个线程使用CAS创建一个傀儡结点;
            //其他线程CAS设置失败,返回false,跳出if方法体;
            //new Node(),是一个Thread为null的结点,也称为傀儡结点,使用CAS设置Head头结点;
           
                if (compareAndSetHead(new Node()))
                //然后再将头结点的引用设置为tail尾结点。
                //即使tail结点指向傀儡结点。
                    tail = head;
            } else {	
            //因为可能多个线程同时进入了这个方法,由于第一个线程创建了傀儡结点,并通过CAS将tail和head结点指向了傀儡结点。
            //所以tail不为空,其他的线程会将Node结点接入傀儡结点的后面
                
                node.prev = t;
                //将结点的前向指针指向队列的尾指针所指向的结点。
                if (compareAndSetTail(t, node)) {
                //再通过CAS将尾指针指向当前结点。
                //将结点的前一个结点的next指针指向当前结点。
                    t.next = node;
                 //返回当前结点。  
                    return t;
                }
            }
        }
    }

5.1.1compareAndSetHead

判断头结点是否为null,如果为null,就把Update对象引用设置为头结点。
这里的Update对象引用是傀儡结点的引用

    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

这样addWaiter执行结束,线程都封装成Node结点,入队了;

在这里插入图片描述

6. acquireQueued(Node node, arg)

这里的arg默认为1;
这个方法的作用:修改Node结点的waitStatus状态、使用LockSupport.unpark()阻塞线程,并等待唤醒;

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋操作;
            for (;;) {
            //predecessor获取当前结点的头一个结点;
                final Node p = node.predecessor();
                //判断P是否为头结点 且 尝试去获取获取一下锁
                //判断state状态是否为0,是则获取锁
                //如果占有锁的线程再这一刻释放了锁,那么队列的头结点会被唤醒
                //并且尝试去获取锁。
                if (p == head && tryAcquire(arg)) {
                //如果获取了锁,将当前结点设置为傀儡结点。
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //如果锁没有释放,上面判断不成立,则进行下面的逻辑;
                //执行shouldParkAfterFailedAcquire将当前结点的上一个结点的waitStatus
                //改为Signal,返回false;在循环执行一次,shouldParkAfterFailedAcquire()
                //返回true;再接着去执行parkAndCheckInterrupt方法,将线程阻塞住。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
6.1 Node.predecessor()

获取当前结点的上一个结点;

  final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
6.2 shouldParkAfterFailedAcquire(p,node)
  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取当前结点的上一个结点的waitStatus;
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
        //如果是-1,即Signal状态,则是可被唤醒装填。可以获得锁;
            return true;
        if (ws > 0) {
       
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
         //这里通过CAS将上一个结点的waitStatus改为Signal装填;
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false;
        return false;
    }

6.3 parkAndCheckInterrupt()

使用LockSupport的park方法将线程阻塞住。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

在这里插入图片描述

7. ReentrantLock.unlock()

线程A执行结束,释放锁了。

//该方法为ReentrantLock中定义的。
public void unlock() {
    sync.release(1);
}

8 sync.release() 方法的执行流程
    public final boolean release(int arg) {
    //tryRelease释放锁,将state变为1,并将占有锁的线程设置为null;
        if (tryRelease(arg)) {
        //获取队列头指针所指向的结点。
            Node h = head;
            //如果头指针指向的结点对象不为空,且结点的waitStatus状态为Signal,即-1;则唤醒头结点。
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            	//唤醒头结点的下一个结点。
            return true;
        }
        return false;
    }
8.1 tryRelease(arg)

将执行的线程设置为Null,再将state的值变为0

    protected final boolean tryRelease(int releases) {
    //获取state的值给C,并设置为0,
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
            //C=0;再将占有锁的线程设置为null;
                free = true;
                setExclusiveOwnerThread(null);
            }
            //复位state的值,即0;
            setState(c);
            return free;
        }
8.2 unparkSuccessor(h)

唤醒傀儡结点的下一个结点,并将傀儡结点的下一个结点的等待状态waitStatus设置为0

//node:传入的是队列头指针所指向的结点对象、即傀儡对象;
 private void unparkSuccessor(Node node) {
		//获取傀儡对象的等待状态waitStatuS
        int ws = node.waitStatus;
        //如果waitStatus小于0,则说明是Signal状态
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
            //将其waitStatus通过CAS改为0;
        Node s = node.next;
        //获取傀儡结点的下一个结点,是真正要被唤醒的结点。
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    	//如果傀儡结点的下一个结点存在,不为Null,则将这个结点里面封装的线程唤醒
    }
9 unpark

唤醒结点后,回到acquireQueued()方法,
线程被阻塞在parkAndCheckInterrupt()方法里。
在这里插入图片描述

parkAndCheckInterrupt():

  private final boolean parkAndCheckInterrupt() {
  //线程被阻塞在这里。
        LockSupport.park(this);
        return Thread.interrupted();
    }

当线程被唤醒后,Thread.interrupted()由于没有发生中断,所以返回false;
继续自旋。

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            //自旋
                final Node p = node.predecessor();
                //唤醒后,再次尝试tryAcquire()方法,
                //如果获取锁成功,则将傀儡结点的下一个结点变为傀儡结点。
                //如果获取锁失败,因为这个时候如果有新的线程刚好申请锁,可能会被这些线程给抢走,继续执行shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法,阻塞。
                if (p == head && tryAcquire(arg)) {
                //如果获取锁成功,则通过将队列头指针指向当前结点(傀儡结点的下一个结点/被唤醒的结点)。
                    setHead(node);
                    //将傀儡结点的后向指针给断掉;
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
9.1 setHead(node)

将头指针指向当前结点(被唤醒的结点)。

   private void setHead(Node node) {
        head = node;
        //将node的封装的线程设置为空;
        node.thread = null;
        //将当前结点的前向指针与傀儡结点断掉;
        node.prev = null;
    }

至此,基于ReentrantLock的AQS源码分析结束;

源码中设计CAS,LockSupport,队列,并发场景的熟练程度要求较高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值