美文网首页
ReentrantLock

ReentrantLock

作者: 囧囧有神2号 | 来源:发表于2018-05-14 14:53 被阅读0次

    AQS是同步器的基础,要先了解AQS的实现
    使用实例:

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    lock.lock();
    try {
      while(条件判断表达式) {
          condition.wait();
      }
     // 处理逻辑
    } finally {
        lock.unlock();
    }
    

    ReentrantLock可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待;
    我们之前介绍过AQS,它是J.U.C包的基础,实现了同步器的主要功能。那么ReentrantLock是怎么利用AQS的?
    这要从ReentrantLock的一个内部类Sync的父类说起,Sync的父类是AQS,Sync的两个实现类分别是NonfairSync和FairSync,由名字可以猜到,一个是用于实现公平锁、一个是用于实现非公平锁。
    那么Sync为什么要被设计成内部类呢?这是文档里建议你使用的方法,子类应该是一个非public的内部类。为什么我想是因为安全,AQS里有很多方法,直接让ReentrantLock继承意味着拥有了操作权,容易误用,所以采用内部类实现的方法;

        public void lock() {
            sync.lock();
        }
        public void unlock() {
            sync.release(1);
        }
    

    ReentrantLock的lock与unlock方法委托给了sync;

        abstract static class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = -5179523762034025860L;
    
            abstract void lock();
    
            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;
            }
    
            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;
            }
    

    构造器:

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

    非公平锁:

    在上面的实例中,使用的就是非公平锁,啥叫非公平?就是插队,举个例子:A,B两个线程,B排在A后等待被唤醒,此时A执行完了同步状态清零,唤醒B,但在这是C抢了进来,B没有争过C,也就是B被C插了队,这是不公平的;
    继承AQS的子类,要实现自己的tryRelease与tryAcquire;

        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
    
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    

    举例:A,B两个线程竞争,A成功同步状态会被设置成1,exclusiveOwnerThread(在AbstractOwnableSynchronizer中);B失败。
    B会调用acquire——>tryAcquire——>nonfairTryAcquire:在该方法中又会再一次判断下同步状态因为A可能执行完了,否则会检查当前线程是否是exclusiveOwnerThread中保存的线程,是则增加同步状态次数(这里是可重入的代码实现),都不是,则放入阻塞队列(逻辑在我的AQS文章中)
    nonfairTryAcquire里实现了可重入的逻辑,与exclusiveOwnerThread保存的线程相等则允许进入,增加次数。

    公平锁

    之前说非公平是因为存在插队,而插队发生在AQS的acquireQueued方法的for循环中

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

    之前的AQS里介绍过,当前线程release将同步状态归零,这是一处被插队的机会;当在等待队列中等待的线程被head唤醒,他会再一次进行判断,就是tryAcquire,在这里就有可能被竞争的线程抢先,即插队;所以要想实现公平锁就得对tryAcquire进行更改,怎么改?不是head的next节点就不能返回true

        static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
    
            final void lock() {
                acquire(1);
            }
    
            /**
             * Fair version of tryAcquire.  Don't grant access unless
             * recursive call or no waiters or is first.
             */
            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;
            }
        }
    

    hasQueuedPredecessors就实现了这个逻辑,下一个线程首先必须是head的next指向的节点才行。

    重入逻辑

    在非公平锁的nonfairTryAcquire里与公平锁FairSync 的tryAcquire里都有如下重入逻辑

    else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0)
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
    

    相关文章

      网友评论

          本文标题:ReentrantLock

          本文链接:https://www.haomeiwen.com/subject/upozrftx.html