重入锁(ReentrantLock)的实现

重入锁(ReentrantLock)

重入锁(ReentrantLock),就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平性和非公平性选择(默认是非公平)。它是一种独占锁。

ReentrantLock常常与synchronized对比分析:

(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。

如果使用自定义的独占锁,重复调用lock则会导致线程阻塞(进入死锁状态)。而ReentrantLock在调用lock方法时,已经获取锁的线程能够再次调用lock取得锁而不被阻塞。

// 不会阻塞
public static void main(String[] args){
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        System.out.println("第一次上锁");
        lock.lock();
        System.out.println("第二次上锁");
        lock.unlock();
        System.out.println("释放第二个锁");
        lock.unlock();
        System.out.println("释放第一个锁");
    }

在这里插入图片描述

// 自定义锁线程阻塞
public static void main(String[] args){
        MyLock lock = new MyLock();
        lock.lock();
        System.out.println("第一次上锁");
        lock.lock();
        System.out.println("第二次上锁");
        lock.unlock();
        System.out.println("释放第二个锁");
        lock.unlock();
        System.out.println("释放第一个锁");
    }

在这里插入图片描述

1.实现重进入

重进入是指任意线程在获取锁之后能够再次获取该锁而不会被锁阻塞

实现重进入需要解决两个问题
  • 线程再次获取锁:锁需要识别获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功
  • 锁的最终释放:线程重复n次获取锁,也需要释放n次锁,随后其他线程才能够获取到该锁。因此锁的最终释放要求锁对于获取进行计数自增,计数表示重复获取的次数,而锁释放时计数自减,当计数为0时,锁释放成功
ReentrantLock的重进入实现

ReentrantLock是通过AQS实现的重进入。

ReentrantLock通过两个内部类来分别实现公平性和非公平性:

在这里插入图片描述

实现原理
1).获取
// 非公平获取,通过NonfairSync中的tryAcquire方法,其内部其实是调用的Sync的nonfairTryAcquire
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

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

// 公平获取
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;
                }
            }
    // 判断是否是持有锁的线程,如果是,同步状态增加并返回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;
        }

可以看到,在同步状态获取时,会在方法内部对线程进行判断,看啥是否是持有同步状态的线程,如果是,则同步状态增加,返回true,表示再次获取成功。

2).释放

ReentrantLock在获取时是增加同步状态,那么在释放时就是减少同步状态

// 公平锁非公平锁都是通过Sync中的tryRelease方法释放
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;
        }

如果一个锁被获取了n次,那么在释放n次之前,锁的free标记都会是false,直到全部释放完即同步状态为0时,才会返回true,表示锁释放成功。

2.ReentrantLock中公平与非公平获取锁区别

锁获取的公平性与非公平性问题

  • 公平性:多个线程按照申请锁的顺序来获得锁。可以理解为锁获取是顺序的(也就是FIFO)。
  • 非公平性:多个线程获取锁的顺序并不一定按照锁的申请顺序,有可能后申请的比先申请的优先获取锁。可能会造成优先级反转或者“饥饿”现象

公平锁的机制没有非公平的效率高,但是公平锁能够减少“饥饿”发生的概率:等待越久的请求越是能够优先满足。

1).非公平锁获取

通过上面的源码可以看到,ReentrantLock非公平锁获取的代码逻辑是:只要CAS设置同步状态成功,就表示线程获取锁。

2).公平锁获取

公平锁则与非公平锁不同之处在于:它在判断条件中多了一个hasQueuedPredecessors()方法。该方法是判断当前节点在同步队列中是否有前驱节点,即是否有比它等待时间更长的节点,如果返回true,这说明有更早到的节点,那么当前节点就获取失败。

3.ReentrantLock为什么默认实现非公平锁

前面提到,非公平锁会引发线程“饥饿”,那么为什么还会默认实现非公平锁?

原因是公平锁每次获取锁都会进行上下文切换(FIFO形式轮流执行),而非公平锁则不会每次都切换,这就会使得非公平性锁的开销远小于公平锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值