Java并发——重入锁ReentrantLock的实现原理及源码解析

1、什么是可重入锁?

可重入锁就是对于已经获得锁的线程,可以重复的多次的获得该锁。

而不可重入的锁在线程获得该锁后,该线程如果再次请求获得该锁,就会在调用tryAquires()的时候返回false,从而阻塞自己。

2、可重入锁的实现原理?

要实现可重入锁的关键有两个,一个怎么识别当前请求锁的线程是不是已经获取锁的线程,另一个因为在一个线程重复的获取了n次锁以后,必须要释放n次锁才能完全释放锁,这怎么实现?

对于第一个问题,因为可重入锁是独占锁,所以只要比较当前线程是不是独占锁的线程,如果是则可以再次加锁,如果不是则返回false。

对于第二个问题,需要一个与锁关联的计数器,来对加锁的重数进行计数,每次获得锁都应该让计数器自增,而每次释放锁都应该让计数器自减,当计数器为0时,表示锁已经成功释放。

源码分析:

那么我们来看看源码:

public class ReentrantLock implements Lock, java.io.Serializable 

▶从继承关系上,可见ReentrantLock是实现了Lock接口,因此Lock接口的功能ReentrantLock也有Lock接口的可中断等待锁的线程,可以实现Condition等功能。

 private final Sync sync;
 abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        abstract void lock();
        //重写nonFairAquire
         final boolean nonfairTryAcquire(int acquires) {
            //省略此处,在公平锁和非公平锁的实现中会讲到
        }
        //重写tryRelease
        protected final boolean tryRelease(int releases) {
            //省略此处
        }

从实现上可以看出,ReentrantLock是通过组合自定义同步器来实现锁的获取与释放,在ReentrantLock自定义了内部类sync,内部类sync继承了AbstractQueuedSynchronizor,并重写了nonfairTryAquire()和tryRelease()方法来获得锁和释放锁。

▶然后再看ReentrantLock的构造方法:

public ReentrantLock() {
        sync = new NonfairSync();
    }

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以看出,ReentrantLock默认实现非公平锁,如果传入表示公平的boolean变量fair,那么fair为true的时候,实现公平锁,fair为false的时候,实现非公平锁。而在ReentrantLock中实现公平锁和非公平锁都是通过实现一个静态内部类来实现了,该静态内部类继承了sync静态内部类。源码如下所示:

公平锁静态内部类

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

因为在sync内部类中没有重写公平锁的获取锁的方法,因此在公平锁的内部类中重写tryAquire()方法,实现公平锁的获取。

通过tryAquire()方法我们可以看出ReentrantLock的实现流程:首先获取锁的同步状态值,这个值是被volatile修饰的,这样可以保证这个值在多线程之间是可见的,如果锁没有被占用(c==0)那么让队列头的线程来获取锁,如果锁已经被占用,因为ReentrantLock是排他锁,因此可以把当前线程和独占的线程比较,如果当先线程就是独占锁的线程,那么可以获取锁,如果不是,则获取锁失败。

非公平锁静态内部类

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

因为在sync内部类中已经重写了nonFairAquire()方法,因此直接调用nonFairAquire()方法即可。

此外因为在sync内部类中已经重写了tryRelease()方法,因此在这两个子类中都没有重写这个方法,需要的时候直接调用即可。



3、细节:

synchronized关键字也是一种可重入锁,比如synchronized修饰的递归方法,在方法执行时,执行的线程在获得了锁以后仍然能够多次的获得锁,而不会因为锁之前已经被占有被阻塞线程本身。

重入锁与synchronized的区别:

○性能上:

    重入锁是显式的重入,而synchronized是隐式的重入。也就是说重入锁是用户自己添加和释放,由JDK实现,而 synchronized由操作系统来添加和释放锁,由JVM实现。因此便利性是synchronized优于重入锁。

     锁的细粒度和灵活度:重入锁优于synchronized

     synchronized在优化前,性能远不如重入锁,但是在synchronized引入偏向锁后、轻量级锁(自旋锁)后,二者的性能区别就不大了,在两者都可用的情况下,官方甚至建议使用synchronized。

○功能上:

    因为重入锁实现了lock接口,因此提供了Condition类,可以实现分组唤醒需要唤醒的线程,而不是像synchronized一样要么随机唤醒,要么全部唤醒。

    重入锁可以实现公平锁,但是默认的是实现非公平锁;而synchronized只能实现非公平锁;(关于公平锁与非公平锁,参见:点击打开链接

     重入锁提供了一种能够终端等待锁的线程,通过lock.lockInterruptibly( )实现。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值