独占式锁acquire()源码剖析

在Lock接口出现之前,我们使用synchronized关键字来实现锁功能,但在JDK1.5之后,java.util.current并发包中增加了Lock接口,提供了与内建锁完全不同的实现多线程共享资源访问机制,增加了可中断的获取锁以及超时获取锁以及共享锁等内建锁不具备的特性,但必须显式进行加锁和解锁过程。

如代码所示,当我们进行加锁操作时:

public class TestLock {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        try{

        }finally{
            lock.unlock();
        }
    }
}

实际上是调用了ReentrantLock的lock()方法,让我们来看源码:

public void lock() {
        sync.lock();
    }
final void lock() {
           // 使用CAS操作尝试将当前同步状态置为1
            if (compareAndSetState(0, 1))
               // 成功即当前线程获取到了同步状态,把当前线程置为持有锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
              //不成功则执行acquire()
                acquire(1);
        }

从上面的代码可以看出,acquire()即是我们获取独占式锁的核心方法。

下面我们来分析一下acquire()方法:

public final void acquire(int arg) {
        // 首先尝试获取同步状态,如果成功则直接返回
        if (!tryAcquire(arg) &&
           // 如果失败则进行如下操作
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

由上面的代码我们可以看到,如果获取同步状态失败,我们先调用addWaiter()方法,在读源码之前我们要清楚,Lock体系的核心AQS中的同步队列是一个带有头尾节点的双向队列,AQS通过持有头尾指针来管理同步队列。

private Node addWaiter(Node mode) {
        // 首先将当前节点以指定模式封装为Node节点
        Node node = new Node(Thread.currentThread(), mode);
        //拿到当前队列的尾节点
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // 若同步队列不为空,则使用CAS将当前节点尾插入同步队列
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //若同步队列为空或尾插失败,则调用enq()方法
        enq(node);
        return node;
    }

紧接着来看enq()方法:

private Node enq(final Node node) {
        // 死循环 --> 不断自旋
        for (;;) {
            Node t = tail;
			// 当前队列为空
            if (t == null) { // Must initialize
			    // 完成队列的初始化操作,lazy-load(懒加载模式)
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
				// 不断将当前节点使用CAS尾插入同步队列中直到成功为止
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

由上我们可以看出,若同步队列为空时,enq()方法会完成同步队列的初始化操作;
以及不断CAS将当前节点尾插入同步队列中。

通过以上分析我们可以知道,addWaiter()方法其实就是将当前线程封装为Node节点尾插入同步队列的过程。addWaiter()方法执行之后会返回封装好的Node节点,传给
acquireQueued()方法:

final boolean acquireQueued(final Node node, int arg) {
	    // 设置失败状态,初始化为true
        boolean failed = true;
        try {
		    // 设置中断状态,默认为false
            boolean interrupted = false;
			// 不断自旋
            for (;;) {
			    // 拿到当前节点的前驱节点
                final Node p = node.predecessor();
				// 当前节点的前驱节点是头节点并且再次获取同步状态成功
                if (p == head && tryAcquire(arg)) {
				    // 将当前节点置为头节点
                    setHead(node);
					// 将前驱节点出队
                    p.next = null;
                    failed = false;
                    return interrupted;
                }
				// 前驱节点不是头节点或获取同步状态失败,将当前线程阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
		    // 若获取锁失败,则将当前节点置为取消状态
            if (failed)
                cancelAcquire(node);
        }
    }

到这里就应该清楚了,acquireQueue()完成了两件事情:一是如果当前节点的前驱节点是头节点并且能够获得同步状态,当前线程成功获取到锁并退出;二是如果获取锁失败,就将当前线程设为取消状态,并阻塞当前线程。

现在返回去看最开始的acquire()代码:

public final void acquire(int arg){
     if(!tryAcquire(arg)&&
	     acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
		 selfInterrupt()
 }
  1. 当tryAcquire()尝试获取同步状态成功时直接返回;
    或者acquireQueued()成功获取到锁之后(此时该方法返回false),直接退出。
  2. 当获取tryAcquire()同步状态失败并且acquireQueued()尝试获取锁失败时,
static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

调用selfInterrupt()方法,中断当前线程。

经过上面的分析,画出的acquire()方法的执行流程如下图所示:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值