JUC系列LOCK框架系列之六 核心锁类之ReentrantLock

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC系列LOCK框架系列之六 核心锁类之ReentrantLock相关的知识,希望对你有一定的参考价值。

ReentrantLock

文章目录


可重入指任意线程在获取到锁之后可以再次获取到该锁而不会被该锁阻塞。

可重入锁指支持可以重入的锁,该锁可以支持一个线程对资源的重复加锁。

线程再次获取锁问题: 锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则可以再次获取成功。

锁的最终释放问题:线程重复n此获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求对于获取进行计数自增,计数表示当前锁被线程重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示该锁成功释放。

主要成员

ReentrantLock实现了Lock接口,并在内部创建了一个内部类Sync继承了AQS类。在Sync上又延伸出公平锁和非公平锁。在绝对时间上,先对锁进行获取请求一定先获取到,则该锁是公平的,反之,锁是非公平锁。

构造函数

ReentrantLock的构造函数如下

    // 无参构造函数 默认是非公平锁
    public ReentrantLock() 
        sync = new NonfairSync();
    

    // 有参构造函数 true是公平锁 false是非公平锁
    public ReentrantLock(boolean fair) 
        sync = fair ? new FairSync() : new NonfairSync();
    

Sync类

sync继承了AbstractQueuedSynchronizer。主要还是依托于AQS进行锁的管理。初识AQS

存在如下方法和作用如下:

方法名说明
void lock();获取锁,由子类实现
boolean nonfairTryAcquire(int acquires)非公平方式获取同步状态 true成功获取;false获取失败
boolean tryRelease(int releases)释放状态,true成功释放;false释放失败
boolean isHeldExclusively()判断资源是否被当前线程占有 true代表当前线程占用,false代表其他线程占用
ConditionObject newCondition()创建一个新的condition对象
Thread getOwner()或者当前占用线程,null代表为有占用者线程
int getHoldCount()返回目前占用的状态(次数) 0代表未被占用
boolean isLocked()资源是否被锁了(占用),true已被占用,false未被占用
void readObject(java.io.ObjectInputStream s)自定义反序列化逻辑

源码部分

    abstract static class Sync extends AbstractQueuedSynchronizer 
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs @link Lock#lock. The main reason for subclassing
         * is to allow fast path for nonfair version.
         * 获取锁的方法,由子类具体实现
         */
        abstract void lock();

        /**
         * 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();
            // 0代表目前没有线程竞争该锁
            if (c == 0) 
                // 通过CAS操作占用锁,占用成功的话,直接结束该方法
                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;
        

        //试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
        // 释放同步状态 true表示锁成功释放
        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);
            
            setState(c);
            return free;
        

        // 判断资源是否被当前线程占有
        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();
        

        // Methods relayed from outer class

        // 返回资源的占用线程
        final Thread getOwner() 
            return getState() == 0 ? null : getExclusiveOwnerThread();
        

        // 返回目前占用的状态
        final int getHoldCount() 
            return isHeldExclusively() ? getState() : 0;
        

        // 资源是否被锁了(占用)
        final boolean isLocked() 
            return getState() != 0;
        

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         自定义反序列化逻辑
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException 
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        
    

NonfairSync 非公平锁

继承了Sync,提供了非公平方式的获取方法lock()

   static final class NonfairSync extends Sync 
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         * 获取锁
         */
        final void lock() 
            // 直接尝试CAS占资源,如果成功就将自己变为占用者
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 锁已经被占用,或者set失败  以独占模式获取对象,忽略中断估计排队去了
                acquire(1);
        

        protected final boolean tryAcquire(int acquires) 
            return nonfairTryAcquire(acquires);
        
    

FairSync公平锁

同样继承了Sync,提供了公平方式的获取方法lock()tryAcquire(int acquires)

    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();
            // 0代表无占用者
            if (c == 0) 
                // 如果当前线程位于队列的头部或队列为空,则CAS操作获取锁
                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;
        
    

示例分析

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class FairAndUnfairTest 
    private static Lock fairLock = new ReentrantLock2(true);
    private static Lock unfairLock = new ReentrantLock2(false);


    public static void main(String[] args) throws InterruptedException 
        System.out.println("非公平锁");
        unfair();
        System.out.println("公平锁");
        fair();
    

    public static void unfair() throws InterruptedException 
        testLock(unfairLock);
    


    public static void fair() throws InterruptedException 
        testLock(fairLock);
    

    private static void testLock(Lock lock) throws InterruptedException 

        for (int i = 0; i < 3; i++) 
            Thread thread = new Thread(new Job(lock)) 
                @Override
                public String toString() 
                    return getName();
                
            ;
            thread.setName("t_" + i);
            thread.start();
        
        Thread.sleep(11000);
    

    private static class Job extends Thread 

        private Lock lock;

        public Job(Lock lock) 
            this.lock = lock;
        

        @Override
        public void run() 
            for (int i = 0; i < 2; i++) 
                //  去竞争锁
                lock.lock();
                try 
                    s();
                    Thread.sleep(1000);
                    System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "],第" + (i + 1) + "次执行, 同步队列中的线程" + ((ReentrantLock2) lock).getQueueThreads() + "");
                 catch (InterruptedException e) 
                    e.printStackTrace();
                 finally 
                    // 当前线程释放锁后,唤醒同步队列中的首结点去获取锁(公平式),其他线程会与首结点对应的线程竞争锁(非公平)
                    lock.unlock();
                
            
        

        private void s()
            lock.lock();
            try 
                System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "],重入执行, 同步队列中的线程" + ((ReentrantLock2) lock).getQueueThreads() + "");
             finally 
                // 当前线程释放锁后,唤醒同步队列中的首结点去获取锁(公平式),其他线程会与首结点对应的线程竞争锁(非公平)
                lock.unlock();
            
        

    

    private static class ReentrantLock2 extends ReentrantLock 
        public ReentrantLock2(boolean fair) 
            super(fair);
        

        public Collection<Thread> getQueueThreads() 
            List<Thread> threadList = new ArrayList<>(super.getQueuedThreads());
            Collections.reverse(threadList);
            return threadList;
        
    

执行结果

非公平锁
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第1次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第2次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第1次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第2次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第1次执行, 同步队列中的线程[]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第2次执行, 同步队列中的线程[]
公平锁
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_0],第1次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2, t_0]
获取锁的当前线程[t_1],第1次执行, 同步队列中的线程[t_2, t_0]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[t_0]
获取锁的当前线程[t_2],第1次执行, 同步队列中的线程[t_0, t_1]
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第2次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第2次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第2次执行, 同步队列中的线程[]

  • 什么是可重入,什么是可重入锁? 它用来解决什么问题?
  • ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
  • ReentrantLock是如何实现公平锁的?
  • ReentrantLock是如何实现非公平锁的?
  • ReentrantLock默认实现的是公平还是非公平锁?
  • 使用ReentrantLock实现公平和非公平锁的示例?
  • ReentrantLock和Synchronized的对比?

以上是关于JUC系列LOCK框架系列之六 核心锁类之ReentrantLock的主要内容,如果未能解决你的问题,请参考以下文章

JUC系列LOCK框架系列之五 核心锁类AbstractQueuedSynchonizer

JUC系列同步工具类之CyclicBarrier

JUC系列LOCK框架系列之三 同步工具类 CountDownLatch

JUC系列LOCK框架系列之四 同步工具类Semaphore

JUC系列同步工具类之ThreadLocal

JUC的LOCK框架系列一LOCK框架预览