天天看点

ReentrantLock 源码分析简介Lock接口提供的功能公平锁和非公平锁

ReentrantLock 源码分析

  • 简介
  • Lock接口提供的功能
  • 公平锁和非公平锁
    • 公平锁
    • 非公平锁
    • 公平锁和非公平锁性能对比

简介

ReentrantLock是Java SE 5新增的jdk层面实现可重入锁的功能, 提供了与synchronized类似的同步功能,只是在使用时需要显示的获取和释放锁。虽然缺少了隐私获取和释放锁的便捷性,但是缺拥有了锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字不具备的同步特性。同时也提供了获取和释放锁的灵活性。

Lock接口提供的功能

ReentrantLock实现了Lock接口,因此分析ReentrantLock之前有必要了解下Lock接口定义了哪些基本操作。具体实现参考AQS一文:

Lock接口 API

方法名称 描述
void lock() 获取锁,调用该方法当前线程将会获取锁,当获取锁后从该方法返回
void lockInterruptibly() throws InterruptedException 可中断的获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程
boolean tryLock() 尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException

超时的获取锁,当前线程在以下三种情况下会返回:

1. 当前线程在超时时间内获得了锁

2. 当前线程在超时时间内被中断

3. 超时时间结束,返回false

void unlock() 释放锁
Condition newCondition() 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将会释放锁

公平锁和非公平锁

ReentrantLock提供了两种锁特性:公平锁和非公平锁,公平锁和非公平锁的区别是:

        如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。

        事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够到优先满足。

代码实现:

/** 
  * lock锁的同步控制,子类有公平和非公平两个版本。
  * 该抽象锁同步器主要提供了尝试获取(非公平实现)和尝试释放锁等功能
  * /
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;
                }
            }
            // 通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程
            // 再次请求,则将同步状态值进行增加并返回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;
        }
		// 如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有		   
		// 同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放
	    // 的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
        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;
        }
		// 判断是否当前线程独占锁
        protected final boolean isHeldExclusively() {
            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
        }
    }
           

公平锁

/** 
    * 继承Sync, 并实现公平锁
    * /
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

		// 阻塞获取锁
        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;
        }
    }
           

非公平锁

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

           

公平锁和非公平锁性能对比

在《java并发编程的艺术》书中对公平锁和非公平锁做了测试,其中公平锁的耗时是非公平锁的94.3倍,因此公平锁虽然保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。

继续阅读