Java并发之AbstractQueuedSynchronizer分析

在并发程序中一般使用锁对共享变量进行互斥访问。在java程序中一般用synchronized关键字来实现线程对共享变量的互斥访问。而从JDK1.5以后java并发大师 Doug Lea 开发了AbstractQueuedSynchronizer(下文用AQS代替)组件,使用原生java代码实现了synchronized语义。我们通过介绍AQS最常用的ReentrantLock锁来介绍AQS。

AQS有两种模式:公平模式和非公平模式(抢占式)。在实现自定义锁时只要按照需求实现公平模式API、非公平模式API或两种模式都实现。ReentrantLock分别实现了公平模式和非公平模式。

在使用公平模式时,AQS会自动生成一个FIFO队列。严格按照线程挂起的顺序获得锁使用权。

猜想:既然锁有被占用和释放两种状态,那么就需要一个state字段来标识锁的状态。果不其然,
这里写图片描述
以ReentrantLock公平锁为例。
ReentrantLock锁通常使用模式为:

public int show() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    }

查看lock方法源码:
Lock方法
可以发现它只是简单代理了sync的lock方法。

这里写图片描述
明显的可以看见有两种实现模式。让我们看看公平模式的lock方法实现。

这里写图片描述
acquire()方法在AQS中实现。

这里写图片描述
首先看tryAcquire方法。

这里写图片描述
在AQS中,Doug Lea并没有实现该方法。而需要使用者自己实现。那让我们看看ReentrantLock 公平模式是如何实现该方法的。

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()) {  //ReentrantLock是可重用锁,即锁的占用者可以多次重新进入锁
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);     //使用CAS修改锁的状态
                return true;
            }
            return false;
        }
    }

当获得锁的使用权时会返回true,不能获得锁耳朵使用权时会返回false。当返回false时,addWaiter()方法将当前线程加入等待队列中。

等待队列如图所示:
这里写图片描述
只有head结点指向的结点才能获得锁的使用权。

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;    //快速找到尾结点
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {    //使用CAS方式将当前结点链接到尾结点。
                pred.next = node;
                return node;
            }
        }
        enq(node);      //如果CAS操作没能将当前结点链接到尾结点说明有多个线程产生竞争。此时需要使用自旋方式将当前结点链接到尾结点。
        return node;
    }

将当前线程接入队列后,接着就需要将当前线程挂起。即acquireQueued方法。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();    //获得当前结点的前驱结点
                if (p == head && tryAcquire(arg)) {   //如果前驱是头结点,并成功获取锁状态
                    setHead(node);   //则移除前驱结点,并将当前结点设置为头结点。
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//否则,检查前驱结点的状态,看当前获取锁失败的线程是否需要挂起。
                    parkAndCheckInterrupt())  //如果需要挂起,借助LockSupport的静态方法Park挂起当前线程。直到将其唤醒。
                    interrupted = true;
            }
        } finally {
            if (failed)       //如果抛出异常
                cancelAcquire(node);   //就将当前结点移除等待队列,即请求停止
        }
    }

到此,线程对锁的竞争告一段落。如果获取锁,则继续运行。否则进入等待队列,线程被挂起。

接下来我们看看锁的释放
这里写图片描述

同样代理了sync的unlock方法
这里写图片描述

首先看tryRelease()方法。
这里写图片描述
同样Doug Lea未实现这个方法。查看ReentrantLock中的实现。

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;    //当前state状态 - releases值
            if (Thread.currentThread() != getExclusiveOwnerThread())  //如果当前线程不是锁的占有者则抛出异常。
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {       //如果状态为0 
                free = true;
                setExclusiveOwnerThread(null);  //就将锁占有者设置为空
            }
            setState(c);  //设置状态
            return free;
        }

锁释放成功后,找到AQS的头结点并将其唤醒即可。
这里写图片描述
至此锁的释放操作完成。

只是自己最近研究AQS源码的一点心得,若有不正确的地方请批评指正。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值