大白话讲ReentrantLock源码

什么是ReentrantLock

ReentrantLock是一个基于AQS(抽象队列同步器)实现的一个同步锁,有公平和非公平两种状态,本节内容只讲公平锁内容。

实现ReentrantLock的核心有三个:

1.CAS (Compare And Swap):比较并交换,用来保证无论并发有多高,都只有一个线程
能够执行成功;

2.自旋 :其实就是死循环;

3.LockSupport的park()和unpark() :作用是用于停止线程和唤醒

一.ReentrantLock构造器

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    public ReentrantLock() {
        sync = new NonfairSync();
    }

ReentrantLock类有两个构造器,一个有参,一个无参。无参构造默认创建的时非公平锁.而有参构造,参数是一个boolean值,当参数为true时,创建的是公平锁,为false时,创建的是非公平锁;

公平锁:无论哪个线程过来都要排队,讲究先到先得;
非公平锁:线程进来会先尝试抢占锁,抢占失败才会入队;

二.线程执行lock()方法

	final void lock() {
	    acquire(1);
	}

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

可以看到,当线程执行lock()方法时,其实就是执行 acquire(arg) 这个方法,这个方法里面有四个方法(先说作用,再细讲):

tryAcquire(...) :尝试获取锁。如果获取锁成功就不会执行下面的方法了;
addWaiter(...) :入队方法,获取锁失败会调用这个方法,里面有个自旋,能够保证入队一定成功;
acquireQueued(...):阻塞方法;调用的是LockSupport.park(…)来进行阻塞线程;
selfInterrupt():只有当线程被中断唤醒时,才会调用这个方法,目的是给线程加中断信号;

不过讲方法前还要先把几个重要的东西过一遍:

	private volatile int state;

    private transient Thread exclusiveOwnerThread;

	private transient volatile Node tail; //队列的尾节点

	private transient volatile Node head; //队列的头节点

1.状态器state:用来标注锁是否被持有,被重入了几次. 初始值为0,每重入一次+1

2.exclusiveOwnerThread: 存放的是持有锁的线程

	static final class Node {
		static final Node SHARED = new Node(); //独占
		static final Node EXCLUSIVE = null;	//共享

		//下面四个是节点的生命状态(waitStatus),默认是0
		static final int CANCELLED =  1;  //丢弃的
		static final int SIGNAL    = -1;  //可唤醒的
		static final int CONDITION = -2;  //条件
		static final int PROPAGATE = -3;  //传播

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

还有一个就是CLH队列(同步队列)的节点Node类,一个Node类可以看作由四部分组成,分别是 prev, next, waitStatus, thread (见下图);
在这里插入图片描述

那么接下来就是重点讲这些方法了!!!



1. tryAcquire(1)方法

	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()) {
	        int nextc = c + acquires;
	        if (nextc < 0)
	            throw new Error("Maximum lock count exceeded");
	        setState(nextc);
	        return true;
	    }
	    return false;
	}

tryAcquire(1) 这个方法:先判断状态器是否为0,如果为0,则说明该锁还未被持有,接着判断队列中有没有元素,没有就用CAS把状态器设置为1,如果设置成功,说明该线程获取锁成功了,就把变量 exclusiveOwnerThread设置为当前线程。

如果上面的都没有成功,就进入下面的判断,先判断变量 exclusiveOwnerThread 是否是当前线程,为当前线程则抢锁成功,这里就体现了 ReentrantLock 的可重入性,获得锁成功后只需把状态器在原来的基础上 +1 即可;

如果上面两种情况都不符合,就抢锁失败,返回 false,准备进入入队方法。



2.addWaiter(Node.EXCLUSIVE)方法

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

来到入队方法时,先创建一个当前线程的节点,判断一次尾节点是否为空,不为空,就把当前节点的前驱指针( prev )指向前驱节点,接着使用 CAS 把尾节点指向当前节点,如果 CAS 设置成功,就把前驱节点的后驱指针指向当前节点,入队成功;

如果上面的都没成功,就执行 enq(node) 方法:进入自旋,判断尾节点是否为空,为空则使用 CAS 创建一个空节点,并把头节点 head 和尾节点 tail 都指向这个空节点。
如果尾节点不为空,则重复之前的步骤;



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

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

acquireQueued(...)方法的作用是阻塞,当然在阻塞前会再次去抢锁;直接看代码:
进入自旋,将当前节点的前驱节点赋给常量 p ,判断前驱节点是否为头节点并且尝试抢锁,如果都为 true ,说明抢锁成功,需要将当前节点设为头节点,并把当前的thread 和前驱指针置空,以及前驱节点的后驱指针置空。

如果前面没满足,则进入下面的 if 条件,先判断前驱节点的生命状态为多少,第一次循环,waitStatus 肯定是默认值0,也就进入最后一个else ,这里是把前驱节点的生命状态(waitStatus)设为 SIGNAL(-1) ;设置成功后进入第二次循环,这时候shouldParkAfterFailedAcquire(...) 这个方法就返回true了。

随后进入 parkAndCheckInterrupt() 方法,里面的LockSupport.park(this)代码就是用于阻塞线程的,这个方法的唤醒方式可以是 LockSupport.unpark() ,也可以是线程中断thread.interrupt() ,当使用线程中断唤醒时,这里就会返回true ,就会进入最后一个方法selfInterrupt()



4.selfInterrupt()方法

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

这个方法只有被中断唤醒的线程才能进入,它的目的是为了重新标记线程的中断信号,让我们的代码逻辑可以进行判断;也就是Thread.interrupted()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值