JUC ~~ ReentrantLock 详解

1 介绍

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。

1.1 重入锁的原理

每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

1.2 公平锁

公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。

1.3 类图

JUC ~~ ReentrantLock 详解_第1张图片

从类图可以看到, ReentrantLock 最终还是使用AQS 来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }


	/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

其中Sync类直接继承自AQS , 它的子类NonfairSync和FairSync 分别实现了获取锁的非公平与公平策略。
在这里, AQS 的state 状态值表示线程获取该锁的可重入次数, 在默认情况下, state的值为0 表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时会尝试使用CAS设置state 的值为1,如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置为2, 这就是可重入次数。在该线程释放该锁时,会尝试使用CAS 让状态值减1,如果减1后状态值为0,则当前线程释放该锁。

2 方法

2.1 void lock( )方法

当一个线程调用该方法时,说明该线程希望获取该锁。如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程, 并设置AQS 的状态值为1 ,然后直接返回。如果当前线程之前己经获取过该锁,则这次只是简单地把AQS 的状态值加1 后返回。如果该锁己经被其他线程持有,则调用该方法的线程会被放入AQS 队列后阻塞挂起。

public void lock() {
        sync.lock();
    }

在如上代码中, ReentrantLock 的lock( )委托给了sync 类,根据创建ReentrantLock构造函数选择sync的实现是NonfairSync 还是FairSync ,这个锁是一个非公平锁或者公平锁。

2.1.1 非公平锁:NonfairSync

final void lock() {
    		// cas 设置状态值
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //调用 AQS的acquire 方法
                acquire(1);
        }

在代码(1)中,因为默认AQS 的状态值为0,所以第一个调用Lock 的线程会通过CAS 设置状态值为1, CAS 成功则表示当前线程获取到了锁, 然后setExclusiveOwnerThread 设置该锁持有者是当前线程。

如果这时候有其他线程调用lock 方法企图获取该锁,CAS会失败,然后会调用AQS的acquire方法。注意,传递参数为1 ,这里再贴下AQS 的acquire 的核心代码。

public final void acquire(int arg) {
    	//(3) 调用ReentrantLock重写的tryAcquire方法
        if (!tryAcquire(arg) &&
            // tryAcquiref返回false会把当前线程放入AQS 阻塞队列
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

之前说过,AQS 并没有提供可用的tryAcquire 方法, tryAcquire 方法需要子类自己定制化,所以这里代码(3)会调用ReentrantLock 重写的t可Acquire 方法。我们先看下非公平锁的代码。

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

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
    // (4)当前AQS状态值为0
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
    //(5)当前线程是该锁持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
    //(6)
            return false;
        }

2.1.2 公平锁:FairSync

重点看公平锁的FairSync 重写的tryAcquire 方法。

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
        // (7)当前AQS状态值为0
            if (c == 0) {
                //(8) 公平性策略
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
        //(9)当前线程是该锁持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
    	//(10)
            return false;
        }

2.2 void lockInterruptibly( )方法

该方法与lock( )方法类似,它的不同在于,它对中断进行响应,就是当前线程在调用该方法时,如果其他线程调用了当前线程的interrupt( )方法, 则当前线程会抛出InterruptedException异常, 然后返回。

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
    //中断抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            // 调用AQS 可被中断的方法
            doAcquireInterruptibly(arg);
    }

2.3 boolean tryLock( )方法

尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁井返回true,否则返回false 。注意,该方法不会引起当前线程阻塞。

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

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

2.4 boolean trylock(long timeout , TimeUnit unit )方法

尝试获取锁,与tryLock( )的不同之处在于,它设置了超时时间,如果超时时间到没有获取到该锁则返回false 。

2.5 释放锁:void unlock()方法

尝试释放锁,如果当前线程持有该锁, 则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0 ,则当前线程会释放该锁, 否则仅仅减1而己。如果当前线程没有持有该锁而调用了该方法则会抛出illegalMonitorStateException 异常,代码如下。

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

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
    
        //如果不是锁持有者调用UNlock抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
    //)如采当前可重入次数为0,则清空锁持有线程
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
    //设置可重入次数
            setState(c);
            return free;
        }

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