• Java并发(九):重入锁 ReentrantLock


    先做总结:

    1、为什么要用ReentrantLock?

    (1)ReentrantLock与synchronized具有相同的功能和内存语义;

    (2)synchronized是重量级锁,性能不好。ReentrantLock性能好;

    (3)ReentrantLock可操作性强,如:实现条件Condition,读写锁,可轮询,使用更灵活。

    2、ReentrantLock实现原理

    (1)ReentrantLock的属性sync是一个Sync(继承了AQS)对象

    (2)获取锁的标志:

        sync.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))

        sync.exclusiveOwnerThread == Thread.currentThread()

    (3)Sync重写了tryAcquire()方法(获取锁)和tryRelease()方法(释放锁),其实就是对sync.state和sync.exclusiveOwnerThread的操作。

    (4)获取不到锁的线程加入sync的同步队列,上一篇说过 Java并发(八):AbstractQueuedSynchronizer

    3、公平锁与非公平锁

    ReentrantLock只能时公平锁和非公平锁中的一个

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

    不同:

    (1)非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。

    (2)非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法。在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁(即使同步队列中有线程等待),而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

    公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

    因此,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

    两个不同的源码:

        /**
         * 不同一:
         * 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。
         */
        // NonfairSync
        final void lock() {
            if (compareAndSetState(0, 1)) // 不同一:NonfairSync会CAS尝试获取锁
                setExclusiveOwnerThread(Thread.currentThread()); // 如果拿到锁就设置当前线程
            else
                acquire(1);
        }
    
        // FairSync(FairSync不会先尝试拿锁)
        final void lock() {
            acquire(1);
        }
    
        /**
         * 不同二:
         * 如果发现锁这个时候被释放了(state == 0),
         * 非公平锁会直接 CAS 抢锁(即使同步队列中有线程等待),而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
         */
        // NonfairSync
        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;
        }
    
        // FairSync
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { // 即使锁没有被占用,也要排在同步队列中等待的线程之后
                if (!hasQueuedPredecessors() && // CLH队列为空或者队列头结点是当前线程节点 才能获得锁
                        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;
        }

    一、ReentrantLock类结构

    public class ReentrantLock implements Lock, java.io.Serializable {
        private final Sync sync; // 锁 大部分功能都是委托给Sync来实现的
        abstract static class Sync extends AbstractQueuedSynchronizer {}
        static final class FairSync extends Sync {}
        static final class NonfairSync extends Sync {}
    }

    二、以NonfairSync为例解析重入锁

    获取锁标志:

    (NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer)

    1.AbstractQueuedSynchronizer.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))

    2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread()

    获取锁:

    public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();// 默认是非公平锁
            lock.lock();
        }
        
        // ReentrantLock
        public void lock() {
            sync.lock();
        }
        
        // NonfairSync
        final void lock() {
            if (compareAndSetState(0, 1)) // 尝试获取锁
                setExclusiveOwnerThread(Thread.currentThread()); // 如果拿到锁就设置当前线程 
            else
                acquire(1);
        }
        
        // AbstractQueuedSynchronizer
        public final void acquire(int arg) {
            if (!tryAcquire(arg) && // 尝试获取锁
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 没有获取到锁,将线程加入同步队列(参考上一篇AbstractQueuedSynchronizer)
                selfInterrupt();
        }
        
        // NonfairSync
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        
        /**
         * 获取锁标志:
         * (NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer)
         * 1.AbstractQueuedSynchronizer.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))
         * 2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread()
         * Sync(NonfairSync没有重写nonfairTryAcquire)
         */
        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;
        }

    释放锁:

        // ReentrantLock
        public void unlock() {
            sync.release(1);
        }
        
        // AbstractQueuedSynchronizer
        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h); // 唤醒队列下一个节点线程 参考上一篇:AbstractQueuedSynchronizer
                return true;
            }
            return false;
        }
        
        // Sync
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false; // 重入锁,直到state==0才算释放
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

    三、公平锁与非公平锁

        // FairSync(NonfairSync会先尝试拿锁,FairSync不会)
        final void lock() {
            acquire(1);
        }
    
        // FairSync
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() && // CLH队列为空或者队列头结点是当前线程节点 才能获得锁
                    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;
        }
        
        /**
         * AbstractQueuedSynchronizer
         * true - CLH队列为空或者队列头结点是当前线程节点
         */
        public final boolean hasQueuedPredecessors() {
            Node t = tail;
            Node h = head;
            Node s;
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }

    以上代码可以看出,公平锁和非公平锁只有两处不同:

    (1)非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。

    (2)非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法。在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

    公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

    因此,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

    四、ReentrantLock优势

    ReentrantLock与synchronized具有相同的功能和内存语义。

    1、与synchronized相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。

    2、ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合。

    3、ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁些。

    4、ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。

    5、ReentrantLock支持中断处理,且性能较synchronized会好些。

     

    参考资料 / 相关推荐

    【死磕Java并发】—–J.U.C之重入锁:ReentrantLock

    一行一行源码分析清楚AbstractQueuedSynchronizer

    Java并发(八):AbstractQueuedSynchronizer

  • 相关阅读:
    bzoj 3238
    bzoj 3473 后缀自动机多字符串的子串处理方法
    bzoj 2998 第k小字串
    bzoj 3672 利用点分治将CDQ分治推广到树型结构上
    bzoj 3671 贪心
    NOIP模拟题——nan
    NOIP模拟题——kun(栈)
    hduP2586——How far away ?
    DP习题
    NOIP模拟题——来自风平浪静的明天
  • 原文地址:https://www.cnblogs.com/hexinwei1/p/9934830.html
Copyright © 2020-2023  润新知