美文网首页JavaJava 程序员
Java并发之ReentrantLock 与 synchroni

Java并发之ReentrantLock 与 synchroni

作者: 程序花生 | 来源:发表于2022-05-14 16:43 被阅读0次

    ReentrantLock

    ReentrantLock直译为重入锁,又称为递归锁。

    是指在同一个线程中,外部方法获得锁之后,内层的递归方法依然可以获取该锁
    倘若锁不具备可重入性,那么我们在第二次获取锁的时候就会造成死锁

    ReentrantLock的实现是基于AQS的,实现了锁机制和重入机制

    ReentrantLock在底层有两种实现方式,分别是公平锁(FairSync)和非公平锁(NonfairSync)

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

    我们在实例化ReentrantLock对象的时候,可以给他传一个boolean类型的变量,如果什么都不传,那么默认生成非公平锁。

    公平锁

    我们先来看一看公平锁的具体实现

    这是公平锁的简易执行流程

    是不是看起来特别简单,所以真正的底层实现也不是很难

    //这是FairSync的lock方法,用于获取锁,它在这里直接调用了AQS的加锁方法
    final void lock() {
                acquire(1);
            }
    

    这里我们看一下AQS的acquire方法。

    public final void acquire(int arg) {
       if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
    }
    

    这里先尝试获取锁,如果获取失败,就会把当前线程加入到线程等待队列,而这里的acquireQueued()和addWaiter()方法分别用于请求入作和封装为Node结点,最后执行selfInterrupt()方法,来中断线程

    非公平锁

    我们再来看一看NonfairSync(非公平锁)的实现

    它的简易流程大概如图:

    同样的,我们看一看NonfairSync的lock方法

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    

    我们可以看到先使用CAS的操作,将当前锁的状态由0改为1,如果该锁的状态不为0,那么和将和公平锁的执行流程一样,直接去调用AQS的acquire方法,如果修改成功的话,证明没有其他线程持有该锁,当前线程可以直接获取该锁。

    公平锁和非公平锁

    公平锁直接去调用AQS的acquire方法,而非公平锁先去验证当前锁的状态,倘若为0,修改为1之后,去执行setExculsiveOwnerThread()方法,也就是当前线程持有该锁

    ReentrantLock的重入性

    说到ReentrantLock的重入性之前我们不妨先了解一下互斥锁和自旋锁

    互斥锁:通过阻塞线程来进行加锁,中断阻塞来进行解锁。
    自旋锁:线程保持运行状态,用一个循环体不停地判断某个标识的状态来确定加锁还是解锁

    在上面者两种锁当中,如果出现了一种情况,就是说,如果A线程给资源B加锁了,然后A线程中还有一个子方法C,需要使用资源B,此时,会出现死锁,因为C在给B加锁的时候,会发现B已经被加锁了,此时会导致C阻塞,而可重入锁就是解决的这个问题!

    在ReentrantLock中式如何处理重入性的呢?

    我们其实很容易想到该怎么实现重入这样的特性,类比我们生活中,就比如我们要从家门口到我们自己的卧室,我们需要先打开大门的锁,进入家里,然后打开客厅的门锁,进入客厅,最后要打开卧室的门锁,进入卧室,这样我们的目的就达成了。

    同样的,我们出门就像我们释放锁的过程,我们需要先出卧室然后锁上卧室的门,然后出客厅,锁客厅的门,最后,出大门,锁上大门。

    而在ReentrantLock中是通过tryAcqurie()方法完成重入功能的

    这里我们以非公平锁的tryAcqurie()方法为例

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

    与之对应的既然有加锁,那肯定就有释放锁,我们可以看tryRelease()方法

    同样的这里我们以非公平锁的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;
    }
    

    synchronized

    使用

    • 修饰在方法上
      作用:进入该方法前,需要先获得当前实例的锁

    • 修饰在静态方法上
      作用:进入该方法前,需要先获得当前类对象的锁

    • 修饰在代码块上
      作用:指定加锁对象,进入该代码块前需要先获取该对象的锁

    在我们使用synchronized的时候,我们需要知道,synchronized修饰的方法,无论执行成功或者异常退出都会释放掉该锁

    原理

    在synchronized关键字修饰过的方法或者代码块上,在经过编译之后会在它们前后分别生成monitorentermonitorexit这两个指令,我们来细细道来这两个指令到底是什么意思,什么用途 这两个指令其实就是操作的一个monitor计数器(监听器)对象

    monitorenter

    当执行这行指令的时候,会查看monitor计数的值,

    • 当monitor的值为0的时候,证明目前还没有被获取,那么这个线程会获取锁,然后monitor的值+1,然后其他线程就不能再获取锁了
    • 如果该线程已经拿到了该锁,然后又重入了这把锁,那么这个monitor的值就会累加,一直累加
    • 当monitor的值大于0那么证明,这把锁已经被别的线程获取了,等待锁的释放
    monitorexit

    该指令其实就是释放锁的过程,而该过程也十分简单,就是将monitor的值依次减一直到monitor的值为0

    优缺点

    synchronized是非公平锁对象,这样可能导致,新的进程一来就获得了锁,而等待很久的进程迟迟无法获得锁,这样有利于提高性能,但是却可能导致进程饥饿现象

    不过synchronized使用起来非常方便直接在方法上添加该关键字就可以了

    ReentrantLock完全可以替代synchronized,synchronized在加锁和释放锁的时候是固定的,而ReentrantLock在加锁和释放锁就十分的灵活,并且synchronized只能使用非公平锁,而ReentrantLock可以灵活的使用非公平锁和公平锁!

    作者:亚雷
    链接:https://juejin.cn/post/7096862549634711566
    来源:稀土掘金

    相关文章

      网友评论

        本文标题:Java并发之ReentrantLock 与 synchroni

        本文链接:https://www.haomeiwen.com/subject/lkcmurtx.html