JDK源码-AQS独占锁分析

8 篇文章 0 订阅

概述

AQS是Java中除了synchronized锁,其他锁的基类,掌握了其原理,对我们了解其他锁事半功倍。
AQS通过volatile int state和FIFO队列,来实现我们期望的同步需求,子类通过继承同步器并需要实现它的方法来管理其状态,在多线程中对状态的操作必须保证原子性,因此需要用到如下方法

    private volatile int state;
    
    protected final int getState() {
        return state;
    }
    //调用此方法来实现原子操作
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
    

有了保证原子性还不够还要保证同步,在AQS中通过Node对象来保证同步性,代码如下:


private transient volatile Node head;
private transient volatile Node tail;

    /**
    节点的状态。其中包含的状态有:
    CANCELLED,值为1,表示当前的线程被取消;
    SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
    CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
    PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
    值为0,表示当前节点在sync队列中,等待着获取锁。
    **/
    static final class Node {
      //前置节点
        volatile Node prev;
     // 后置节点
        volatile Node next;
     //入队列时的当前线程
        volatile Thread thread;
      //存储condition队列中的后继节点。先不要过段关注这个
        Node nextWaiter;
    }

对于锁的获取,请求的节点挂载在节点的尾部也就是tail,而锁的释放在获取是是从head开始的。

排它锁实现细节

要实现排它锁的必须实现两个接口,

方法名描述
protected boolean tryAcquire(int arg)排它锁的获取
protected boolean tryRelease(int arg)排它锁的释放
示例
package com.example.demo;

class Mutex implements Lock, java.io.Serializable {

    private static class Sync extends AbstractQueuedSynchronizer {
        //获取锁
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            //更改state的状态为1,如果更改成功,保存当前线程
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //释放锁
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            //更改为0,因为这个地方没有锁的竞争,所以不用compareAndSetState
            setState(0);
            return true;
        }

       ....
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }


    public void unlock() {
        sync.release(1);
    }
     ...
    }
    
}

可以看到通过继承AQS实现一个独占锁还是很简单的。

实现分析

acquire方法

可以看到我们在获取锁的时候调用了lock方法,而lock方法又调用了acquire方法,让我们看看acquire实现细节

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

可以看到在acquire方法中首先调用tryAcquire方法进行锁的获取,如果获取成功直接退出,获取不成功则调用addWaiter产生了node,并作为参数传递给了acquireQueued方法。先分析下addWaiter

    private Node addWaiter(Node mode) {
        
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //第一次进来这里为空,直接进入enq,否則 //把node插入到tail处,并node.prev为上一个tail,其次prev.next等于node
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
    //高并发下有可能一次执行不成功,那么需要自旋尝试更改
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //先创建head节点,并把head节点等于tail节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //把node插入到tail处,并node.prev为上一个tail(如果是第一次创建,为head),其次prev.next等于node
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

  1. 第一次进来这里为空,直接进入enq,否則把node插入到tail处,并node.prev为上一个tail,其次prev.next等于node
  2. 如果tail节点为空,先创建head节点,并把head节点等于tail节点
  3. 把node插入到tail处,并node.prev为上一个tail(如果是第一次创建,为head),其次prev.next等于node

acquireQueued方法

   final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取当前节点上一个节点
                final Node p = node.predecessor();
                //如果上一个节点等于head,在此尝试获取锁,如果获取成功设置当前节点为头部节点。
                if (p == head && tryAcquire(arg)) {
                
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //如果上一个节点不等于头节点或者是头节点但是没有获取成功,那么更改SIGNAL为-1,并对当前线程进行了park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    
     private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
  1. 获取当前节点上一个节点
  2. 如果上一个节点等于head,在次尝试获取锁,(因为执行在这里的时候,head节点的锁已经释放,所以进行了在此获取)如果获取成功设置当前节点为头部节点。
  3. 如果上一个节点不等于头节点或者是头节点但是没有获取成功,那么更改SIGNAL为-1,并调用parkAndCheckInterrupt方法进行阻塞。
release方法

在unlock的时候,调用了release进行了锁的释放

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
      private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 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);
    }

  1. 调用tryRelease更新state状态
  2. 更改head的waitStatus为0,并调用unpack唤起当前节点的后续节点。

总结

加锁
  1. 调用compareAndSetState更改state状态,如果更改成功,那么获取锁成功
  2. 如果未更改成功,判断前驱节点是否为头结点,如果是尝试在次获取锁,如果获取成功那么设置为当前节点为头结点。
  3. 如果获取失败,当前线程进行休眠,等待唤醒。
释放锁
  1. 更改state状态,如果更改成功,调用unpack唤起后续节点。

参考

http://ifeve.com/introduce-abstractqueuedsynchronizer/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值