java并发编程之AbstractQueuedSynchronizer-独占式锁的实现源码分析

java 多线程中实现锁的类型
主要有两种 一种是使用synchronize

第二种是自己实现Lock接口或者直接使用系统实现Lock接口的ReentantLock。

下面主要讨论第二种方式:实现Lock接口

下面是一个实现了Lock接口的自定义锁

public class MyLock implements Lock {

    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0的时候获取锁
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        // 释放锁,将状态设置为0
        @Override
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new
                    IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        // 返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }


    // 仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

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

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

我们可以看到最终是通过继承AbstractQueuedSynchronizer(AQS)类的来实现加锁和获取锁的。

那么AQS是怎样实现多线程下的加锁呢?

首先结构上它是一个FIFO (First-in, First-out,先进先出)队列,图像如下图所示:
在这里插入图片描述

每次添加节点时

​ 1.将新节点的prev指向tail节点

​ 2.将 tail指向新节点。

​ 3.将原tail节点的next指向新节点。

AQS中独占式锁的实现

首先acquire方法

首先源码如下

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *  以独占模式获取,忽略中断,实现由至少执行一次tryAcquire,成功时才返回。否则这个   * 线程会在排队,可能会重复的加锁和解锁,会一直执行tryAcquire直到成功。
 *  这个方法可以被使用到实现Lock.lock方法
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

方法注释的翻译:

 以独占模式获取,忽略中断,实现由至少执行一次tryAcquire,成功时才返回。否则这个线程会在排队,
 可能会重复的加锁和解锁,会一直执行tryAcquire直到成功。
 这个方法可以被使用到实现Lock.lock方法

解释:

可以看到accquire方法先执行 tryAcquire如果成功就继续了,没有成功的话就会执行将当前线程放到队列中执行。

下面再看看 acquire中的addWaiter方法

addWaiter方法

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    //快速的将当前节点添加到尾结点
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //快速添加不成功,使用自旋方式添加节点
    enq(node);
    return node;
}
 /**
     * Inserts node into queue, initializing if necessary. See picture    above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果队列为空初始化队列
            if (t == null) { // Must initialize
           //这里设置的头结点可以认为是当前正在执行线程的节点 对于AQS来说必须让头结点为正在执行的节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

上述的代码,主要还是通过使用compareAndSetTail(Node expect,Node update)方法来确保节点能够被线程安全添加。(compareAndSetTail这个具体的实现是JNI调用操作系统的原生程序,这里就不深入了.), 最后 enq(final Node node)方法将并发添加节点的请求通过CAS变得“串行化”了。

当节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程),这个实现就是acquireQueued方法

acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//标记是否成功拿到资源
    try {
        boolean interrupted = false;//标记等待过程中是否被中断过

        //这是一个假的“自旋”!
        for (;;) {
            final Node p = node.predecessor();//拿到前驱
            //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。 
            // head是一个Lock对象的全局变量  private transient volatile Node head;
            if (p == head && tryAcquire(arg)) {
                setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
                p.next = null; // 说明前一个锁线程已经不再队列中了 setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
                failed = false; // 成功获取资源
                return interrupted;//返回等待过程中是否被中断过
            }

            //如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
        }
    } finally {
    	// 这里应该是解决 tryAcquire 成功但 failed未修改的情况吧,具体没看懂为什么这么做
        if (failed) //或者如这里所说 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
            cancelAcquire(node);
    }
}


可以看到 acquireQueued的主要逻辑是:判断当前节点的前节点是否是头节点,如果是就尝试获取同步状态,获取成功就将自己设置为头节点。

顺便如果当前节点的前驱不是头节点,当前线程会被中断,等待执行节点的release,来唤醒所有的排队节点进行执行节点的检查。

下面是AQS整个独占式状态的获取流程:在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值