Java线程并发中的锁——ReentrantLock(重入锁)原理详解

ReentrantLock是一个重入锁,可以支持一个线程对资源重复加锁,他还支持公平加锁和非公平加锁。synchronized关键字也隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

公平锁

定义

公平加锁是在绝对时间上先对锁获取的请求一定先被满足,公平的获取锁也就是等待时间最长的线程最优先获取锁,也可以说获取锁时顺序的。ReentrantLock提供了可以控制是否公平锁的构造函数。
优点和缺点
没有非公平锁效率高,但是能够减少饥饿发生的概率,等待越久的线程越容易获取到锁。

实现重进入

重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞
线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
下面我们看看非公平获取同步状态的代码实例:

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

成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放同步状态时减少同步状态,释放锁代码如下

       protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            // 如果当前请求线程不是上次加锁的线程抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果同步状态为0说明锁完全释放
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 减少同步状态,减到0释放锁
            setState(c);
            return free;
        }

如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

公平锁和非公平锁区别

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO
对于非公平锁只要CAS设置同步状态成功就表示当前线程获取了锁,对公平锁肯定不同,你还得考虑那些等待了更久的线程,让我们来看下公平锁的源码:

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

下面我们写一个公平锁和非公平锁区别的代码实例

public class FairAndUnfairTest {
    /**
     * public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        }
     */
    private static Lock fairLock = new ReentrantLockTest(true);
    private static Lock unfairLock = new ReentrantLockTest(false);

    public static void main(String[] args) {
        testLock(unfairLock);
//      testLock(fairLock);
    }

    public static void testLock(Lock lock){
        for(int i=0;i<10;i++){
             new Thread(new Job(lock),i+"").start();
        }
    }

    private static class Job extends Thread{
        private Lock lock;
        public Job(Lock lock){
            this.lock = lock;
        }
        public void run(){
            lock.lock();
            try {
                // 连续多次打印当前Tread和队列中的Thread
                System.out.println("Lock by ['" + Thread.currentThread().getName() + "'],and waiting "+((ReentrantLockTest)lock).getQueuedTheads());
            } finally {
                lock.unlock();
            }
        }

    }

    @SuppressWarnings("serial")
    private static class ReentrantLockTest extends ReentrantLock{
        public ReentrantLockTest(boolean fair) {
            super(fair);
        }
        public Collection getQueuedTheads(){
            List list = new ArrayList(super.getQueuedThreads());
            // 翻转集合顺序
            Collections.reverse(list);
            return list;
        }
    }

}

非公平锁运行结果如下,线程没有获取锁没有按照顺序

Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]
Lock by ['7'],and waiting [Thread[1,5,main], Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main]]
Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['5'],and waiting [Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['6'],and waiting [Thread[8,5,main], Thread[9,5,main]]
Lock by ['8'],and waiting [Thread[9,5,main]]
Lock by ['9'],and waiting []

公平锁的运行结果如下,线程按照顺序获取锁

Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]
Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['5'],and waiting [Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['6'],and waiting [Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['7'],and waiting [Thread[8,5,main], Thread[9,5,main]]
Lock by ['8'],and waiting [Thread[9,5,main]]
Lock by ['9'],and waiting []

公平锁多次运行偶尔会出现个别不按照顺序的线程,有时会出现下面结果,有知道原因可以留言,谢谢

Lock by ['1'],and waiting [Thread[0,5,main], Thread[2,5,main]]
Lock by ['0'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['3'],and waiting [Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['5'],and waiting [Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['4'],and waiting [Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['7'],and waiting [Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['6'],and waiting [Thread[9,5,main], Thread[8,5,main]]
Lock by ['9'],and waiting [Thread[8,5,main]]
Lock by ['8'],and waiting []

理论上非公平锁会出现同一个线程连续获取锁,我这里暂时没有模拟出来,如果有知道好的办法可以留言,谢谢
公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量

你可能感兴趣的:(java并发编程,Java并发编程)