ReentrantLock的lock源码分析

77 阅读3分钟

一直在用concurrent包里的东西,最近想研究一下个中细节,先从ReentrantLock提供的集中获取锁的方式开始吧。

1.ReentrantLock简要介绍

简单介绍一下ReentrantLock,可重入锁,互斥锁,提供了fair和unfair两种模式的锁。默认构造函数是unfair的锁,如果初始化时传入true的参数则会返回fair锁。所谓不公平就是在锁可获取时,不用考虑该锁队列是否有其他waiter,直接获取;反之,对于公平锁来讲就是当等待的锁资源可获取时要看下等待队列中当前线程是不是head线程,如果不是则不获取。

简单的看一下获取锁的代码:

if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
//unfair lock nonfairTryAcquire
if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

从上面的代码块可以清晰的看到,公平锁fairLock在获取之前会看下自己是不是没有前继元素。而非公平锁unfairLock并不会。

2.ReentrantLock提供的获取锁的方式 ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()

1)lock()

  sync.lock();
}

当锁可用,并且当前线程没有持有该锁,直接获取锁并把count set为1. 当锁可用,并且当前线程已经持有该锁,直接获取锁并把count增加1. 当锁不可用,那么当前线程被阻塞,休眠一直到该锁可以获取,然后把持有count设置为1. 小结:该种方式获取锁不可中断,如果获取不到则一直休眠等待。

2)tryLock()

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

当获取锁时,只有当该锁资源没有被其他线程持有才可以获取到,并且返回true,同时设置持有count为1; 当获取锁时,当前线程已持有该锁,那么锁可用时,返回true,同时设置持有count加1; 当获取锁时,如果其他线程持有该锁,无可用锁资源,直接返回false,这时候线程不用阻塞等待,可以先去做其他事情; 即使该锁是公平锁fairLock,使用tryLock()的方式获取锁也会是非公平的方式,只要获取锁时该锁可用那么就会直接获取并返回true。这种直接插入的特性在一些特定场景是很有用的。但是如果就是想使用公平的方式的话,可以试一试tryLock(0, TimeUnit.SECONDS),几乎跟公平锁没区别,只是会监测中断事件。 3)tryLock(long timeout, TimeUnit unit)

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

从上面代码中可以看出,获取锁成功或者超时之后返回。而且在公平锁和非公平锁的场景下都可以使用,只是会增加对中断事件的监测。 当获取锁时,锁资源在超时时间之内变为可用,并且在等待时没有被中断,那么当前线程成功获取锁,返回true,同时当前线程持有锁的count设置为1. 当获取锁时,在超时时间之内没有锁资源可用,那么当前线程获取失败,不再继续等待,返回false. 当获取锁时,在超时等待时间之内,被中断了,那么抛出InterruptedException,不再继续等待. 当获取锁时,在超时时间之内锁可用,并且当前线程之前已持有该锁,那么成功获取锁,同时持有count加1. 4)lockInterruptibly()

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

当获取锁时,锁资源可用,那么当前线程成功获得锁,同时持有count设置为1,返回true. 当获取锁时,锁资源可用,当前线程已持有该锁,它成功获取该锁,同时持有count增加1,返回true. 当获取锁时,锁资源不可用,那么该线程开始阻塞休眠等待,但是等待过程中如果有中断事件,那么会停止等待,立即返回.

当获取锁时,锁资源不可用,线程开始阻塞休眠等待,如果等待过程中锁资源变为可用,那么当前线程成功获得锁,同时持有count设置为1,返回true.

小结:

lockInterruptibly()获取锁是以排他的模式获取,一旦被中断就放弃等待获取。在等待开始时首先检测中断状态,然后至少调用一次tryAcquire,成功获取就返回true。否则当前线程就开始排队,并且不断的被blocking、unblocking、invoking tryAcquire 直到获取成功或者被中断为止。