2019-07-09 ReentrantLock实现原理

ReentrantLock主要利用CAS和CLH队列来实现。支持公平锁和非公平锁,两者的实现类似

  • CAS:Compare And Swap,比较并交换。CAS有三个参数:内存值V、预期值A、要修改的新值B。当且仅当预期值A=内部才能值V,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java底层。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。
  • CLH队列:带头结点的双向飞循环链表

RenntrantLock的基本实现可以概况为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入CLH队列并且被挂起。当锁释放之后,排在 CLH队列对首的线程会被唤醒,然后CAS再次尝试读取锁。在这个时候。如果

  • 非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
  • 公平锁:如果同时还有另一个线程进来尝试获取,当他发现自己不是对首的话,就会排到队尾,由队首的线程获取锁。

ReentrantLock的实现
ReentrantLock默认是非公平锁

  • 非公平锁
	final void lock() {
	    if (compareAndSetState(0, 1))
	        setExclusiveOwnerThread(Thread.currentThread());
	    else
	        acquire(1);
	}

在尝试获取锁的时候,会先调用上面的方法。采用CAS去加锁,如果state不为0,说明当前锁被占有,会执行acquire(1)方法,

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. tryAcquire再次尝试获取锁
	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,说明当前没有线程占有锁,尝试加锁,加锁成功,保存当前线程;如果是当前线程获取锁,state+1,这就是重入锁的原理
2. addWaiter加入等待队列

    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;
    }
  1. acquireQueued自选获取锁
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //死循环,正常情况下线程只有获得锁才能跳出循环
        for (;;) {
            final Node p = node.predecessor();//获得当前线程所在结点的前驱结点
            //第一个if分句
            if (p == head && tryAcquire(arg)) { 
                setHead(node); //将当前结点设置为队列头结点
                p.next = null; // help GC
                failed = false;
                return interrupted;//正常情况下死循环唯一的出口
            }
            //第二个if分句
            if (shouldParkAfterFailedAcquire(p, node) &&  //判断是否要阻塞当前线程
                parkAndCheckInterrupt())      //阻塞当前线程
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

加锁流程图
2019-07-09 ReentrantLock实现原理_第1张图片

  • 公平锁
final void lock() {
            acquire(1);
        }

公平锁每次加锁都会从同步队列中获取对首节点

从源码角度彻底理解ReentrantLock(重入锁)

你可能感兴趣的:(Java基础)