JUC之ReentrantLock解读

ReentrantLock简介

ReentrantLock是可重入锁的实现,可重入锁的含义是:如果已经拥有锁的线程再次获取锁时会立即响应成功,这点可以使用isHeldByCurrentThreadgetHoldCount方法来检验。ReentrantLock可重入互斥锁具有同synchronized的隐式监视器锁相同的基本行为和语义,但是其更具有扩展能力。

ReentrantLock提供公平锁和非公平锁的特性,如果设置为公平锁,那么锁倾向于访问等待时间最长的线程;如果是非公平锁,那便不会保证任何特定的访问顺序。公平锁会导致整体的吞吐量降低,不过却可以在最少的时间差内获取到锁和保证锁饥饿。不过请注意,锁是公平并不能保证线程调度的公平性。

ReentrantLock使用

典型用法如下(这里以lock为例,当然也可以用tryLock方法来获取锁):

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

ReentrantLock源码剖析

源码中涉及AQS模板类的调用,可选择先查看《JUC之AQS解读》这篇文章的介绍。

同步器

对于同步状态,依然是继承与AQS来实现的,考虑到有公平锁和非公平锁的特性,其又做了一层抽象,提供一些共用方法,如:

对于lock的加锁过程则交予具体的锁类型同步器去实现

  • 获取非公平锁: nonfairTryAcquire
  • 释放锁:tryRelease
  • 是否当前线程持有锁:isHeldExclusively
  • 条件对象:newCondition
  • 对象序列化:readObject
  • 线程持锁状态:getOwner,getHoldCount,isLocked等

其中获取非公平锁方式放在这个抽象层的原因是为了ReentrantLock在默认非公平性下调用:

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

抽象同步器的源码如下:

    abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
          // 省略
        }

        protected final boolean tryRelease(int releases) {
          // 省略
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }
        
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

公平锁与非公平锁

ReentrantLock公平锁和非公平锁的特性可以通过选择如下构造函数来设定:

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

如果直接使用默认的无参构造函数,那么是用的非公平锁:

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

公平锁和非公平锁的同步状态器源码如下:

    static final class NonfairSync extends Sync {

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThreadcurrentThread(Thread.());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    static final class FairSync extends Sync {

        final void lock() {
            acquire(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;
        }
    }

同时结合前面的抽象同步器代码,可以总结出区别在于:

  • 非公平的加锁在最开始阶段会进行一次资源竞争抢占compareAndSetState(0, 1),如果抢占成功则直接加上锁。
  • 公平性加锁在尝试获取锁的时候,如果state==0的条件下多了一层判断!hasQueuedPredecessors()来保证公平有序。

hasQueuedPredecessors的作用是判断是否有其他线程先于当前线程进行等待,避免非公平抢占。其源码如下,通过判断是否有等待节点来告知:

    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

可重入性

对于可重入性,就是在判断到当前线程持有锁的时候,再将状态值进行加法:

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

对于可重入的次数而言,由于继承的是AQS并使用其state字段,所以最大值只能到Integer.MAX_VALUE,超出此限制会抛出Error

    /**
     * The synchronization state.
     */
    private volatile int state;

锁释放

回到抽象同步器的tryRelease方法,这是对锁释放的一个操作,其完成的功能有如下3步:

  1. 判断是否是当前线程,不是则抛异常;
  2. 释放状态值:state - releases;
  3. 如果状态值为0,则释放排他锁;
  4. 设置状态值并返回;
        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;
        }

根据AQS模板类的使用,会根据如下调用流程走完锁释放流程:

ReentrantLock.unlock -> aqs.release -> ReentrantLock.tryRelease

你可能感兴趣的:(Java)