并发编程------ReentrantLock源码(一)

在前面的并发编程------CAS提到了手写lock锁,那我们现在来看看ReentrantLock的源码。

以下代码是查看的ReentrantLock(false),也就是非公平锁

ReentrantLock
ReentrantLock属于重入锁,先来看看他的无参构造方法。
在这里插入图片描述
可以看到默认为非公平锁,那么我们今天主讲非公平锁。
那我们要怎么才能使用公平锁?其实很简单:
在这里插入图片描述
这个有参构造可以看到,只要参数为true,则创建的就是公平锁,为什么主讲非公平锁?因为他的效率比公平锁高。因为我们使用的CPU资源去运行线程的,非公平和公平的差异主要在于:线程切换的开销,其实就是非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。

好了,说了这么多,直接进入正题吧。



常用参数
在了解之前,需要找到他的数据存储方式。

在这里插入图片描述
我们可以看到非公平锁的父类是AQS,这也是我们熟悉的通读队列器,其实数据的存放是AQS定义的一个Node对象

volatile Node prev;
volatile int waitStatus;
volatile Node next;
volatile Thread thread;

从这几个变量名我们可以知道分别是:上一个节点、等待状态、下一个节点、线程。
在涉及到上下节点,这应该也会知道这是一个双向链表,那么一旦涉及到双向链表就需要有全局的头节点和尾节点。所以我们可以找到

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

这些参数源码看多了,自然就很熟悉了。毕竟这些大部分都是双向链表必备的一些参数。

还有一个很重要的参数:

private volatile int state;

这个参数后面在解释

重要的参数看完了。来看看锁的获取和释放。



lock()
在这里插入图片描述在这里插入图片描述
到达这里应该就很熟悉了:

1.compareAndSetState

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

利用CAS的原子性质,来修改数据state(0表示没有线程获取到锁,1表示已经有线程获取到锁,可以为2,3,4…因为重入一次+1),达到锁的效果。

一旦锁修改成功,那么代表这个线程获取到了锁,如果没有则进行acquire(1);

2.acquire

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

tryAcquire

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

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

可以看到最后此方法执行的是两个判断,如果当前的state状态为0,那么就允许尝试修改数据,并且将当前获取到锁的线程记录下来。

current == getExclusiveOwnerThread()如果当前线程本来就是获取到锁的线程,那么就将state+1

如果返回false,则执行

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;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

static final int SIGNAL    = -1;

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

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

addWaiter(Node mode)建立新的节点,并且将新节点的上一个节点变为全局变量的尾节点,通过CAS将尾节点改为新节点,一旦尾节点更新成功,那么就将原来的尾节点的下一个节点设置为新节点。这里就是将新的节点链接到链表上去。

enq(final Node node)如果尾节点为null,那么就创建新节点并设置为尾节点,至于为什么头节点没有设置,后面的文章会提到,else里面的做法也是一样的,在尾节点将新节点添加进去,因为在尾节点为null的情况下,addWaiter(Node mode)中的if()里面的代码块并没有进行。

acquireQueued(final Node node, int arg)利用变量p存放node的上一个节点,那么第一个if判断就没必要看了,看二个才是重点。调用shouldParkAfterFailedAcquire(Node pred, Node node)方法查看线程的等待状态,因为waitStatus为int类型所以默认值为0,则执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL);方法,以便于后面的线程进入之后,查询到前面的节点是处于ws == Node.SIGNAL的情况下,直接执行parkAndCheckInterrupt()方法,将线程设置为阻塞状态。



unlock()
在这里插入图片描述

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

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);
	}
}

tryRelease(int releases)释放锁,检测是否是获得锁的线程释放,否则抛出异常,如果是获得锁的线程释放,那么检测state值是否为1,不为1,则代表使用了重入性,那么只需要将state设置为c即可,如果为1,则代表此线程需要释放锁了,就将记录锁线程的变量exclusiveOwnerThread设置为null,并返回true。在每一个线程阻塞之前都执行了shouldParkAfterFailedAcquire(p, node)方法,目的就是为了让这个阻塞线程的前一个节点的waitStatus变为-1,在后期线程激活之后,并且需要被激活的线程的上一个节点已经晋升为头结点,这时候获取头结点,他的头节点的waitStatus肯定不为0,所以一定成立。那么就直接执行unparkSuccessor(Node node)方法,从尾节点找到第一个waitStatus 小于等于0的线程,释放此线程。



那为什么说这事一个不公平的呢?他还不是一样只激活了一个线程?

其实,并不然,如果在此线程在激活的过程中,新增了一个线程,那么这个新增的线程就可以和激活的线程争取机会,看谁获得锁,我们看看公平锁里面就知道了,他做了判断的,只能激活的这个线程才能修改state。

在这里插入图片描述

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

这就是保证公平竞争的主要代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙小虬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值