`

java显式锁之--ReentrantLock

阅读更多

前言

 

采用synchronized进行加锁,是由jvm内部实现的 称为:内置锁。从java1.5开始,jdk api引入了新锁API 他们都继承自Lock,称为:显式锁,比如今天的主题ReentrantLock。之所以称做显式锁,主要有两点原因:1、相对于内置锁是有jvm内部实现的,显式锁是在使用java api实现的,确切的说是基于AQS实现的(对AQS的理解可以点击这里);2、使用Lock加锁,需要显式的加锁以及释放锁,相对于内置锁使用synchronized而言,要麻烦些。下面来看看ReentrantLock的基本用法:

 

public class ReentrantLockTest {
 
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try{
            //业务方法
        }catch (Exception e){
            //业务异常
        }finally {
            lock.unlock();
        }
    }
}

 

从表面上看,使用显式锁比使用内置锁更繁琐,需要手动调用lock加锁和unlock解锁,如果忘记unlock 就会导致其他线程永远无法获取到锁的严重错误。既然为何还要新增显式锁呢?

 

简单的讲,显式锁的内置锁的补充:显式锁Lock提供了中断锁、定时锁、可轮询等实现,这些都是内置锁synchronized不具备的;显式锁可以指定为公平锁或非公平锁,而内置锁synchronized锁是非公平的。使用synchronized加锁,有些情况下很容易导致死锁,在这种情况下改用显式锁定时功能 在一段时间没有获取到锁,就放弃获取锁 就可以避免死锁。

 

另外与内置锁还有一个明显不同的地方是,内置锁可以用在方法上,而显式锁只能用在代码块上,也就是说强制使用更细粒度的加锁。

 

可以说内置锁和显式锁是互补关系:显式锁不能用在方法上,而且容易忘记释放锁;内置锁不可中断,有些情况下容易产生死锁,另外内置锁无法实现公平锁。根据这些差异在自己的实际业务场景中选择性使用即可,需要说明下的是显式锁的性能比内置锁性能稍微好些。

 

理论总结到处结束,下面开始以ReentrantLock锁分析下显式锁的实现原理。主要包括:排它锁、公平锁、非公平锁、中断锁、延迟锁、轮询锁、重入锁的实现原理。

 

ReentrantLock实现原理

 

AQS的实现

首先需要说明的ReentrantLock与内置锁一样都是排它锁,从名字上开是与内置锁一样是可重入的。ReentrantLock与前两篇文章中讲的SemaphoreCountDownLatch一样都是基于AQS实现的,只是SemaphoreCountDownLatch都是共享锁的实现方法,而ReentrantLock是排它锁实现,也就是说ReentrantLock的内部类在实现AQS时是对tryAcquiretryRelease这两个方法进行扩展的。首先来看内部类SyncAQS的实现(Sync还有两个子类,分别对应公平和非公平锁实现)

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
 
    //交给公平和非公平的子类去实现
    abstract void lock();
 
    //非公平的排它尝试获取锁实现
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //如果AQS的state为0说明获得锁,并且对state加1,其他线程获取锁时被阻塞
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
       
        //判断线程是不是重新获取锁,如果是 无需排队,对AQS的state+1处理,这就是重入锁的实现
        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;//都不满足获取锁失败,进入AQS队列阻塞
    }
 
    //公平的排它尝试释放锁实现
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;//对应重入锁而言,在释放锁时对AQS的state字段减1
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {//如果AQS的状态字段已变为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;
    }
 
    /**
     * 说明ReentrantLock是可序列化的
     */
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

 

 

非公平锁

非公平锁实现:

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() {
        //判断当前state是否为0,如果为0直接通过cas修改状态,并获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);//否则进行排队
    }
 
    //调用父类的的非公平尝试获取锁
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

 

非公平锁的实现很简单,在lock获取锁时首先判断判断当前锁是否可以用(AQSstate状态值是否为0),如果是 直接“插队”获取锁,否则进入排队队列,并阻塞当前线程。

 

公平锁

公平锁实现:

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();
        if (c == 0) {
            //AQS队列为空,或者当前线程是头节点 即可获的锁
            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;
    }
}

公平锁的实现,跟Semaphore一样在tryAcquire方法实现中通过hasQueuedPredecessors方法判断当前线程是否是AQS队列中的头结点或者AQS队列为空,并且当前锁状态可用 可以直接获取锁,否则需要排队。

 

重入锁

不论是公平锁还是非公平锁的实现中,在tryAcquire方法中判断如果锁已经被占用,都会判断是否是当前线程占用,如果是 可以再次获取锁(无需排队),并对AQSstate字段加1;在释放锁时每次都减1,直到为0时,其他线程在可用。顺便提一下内置锁synchronized也是可以重入的。

//重入锁实现
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
 

 

排它锁

从两个tryAcquire的实现可以看出ReentrantLock的排它锁实现,根本上是通过AQSstate字段保证的,每次获取锁时都是首先判断state是否为0,并且只有1个线程能获取到锁。这个特性与内置锁synchronized相同。

 

关于ReentrantLockAQS的三个内部类实现分析完毕,接下来看下ReentrantLock的核心方法,实现都很简单基本都是直接调用FairSync或者NonfairSyncAQS的实现。比如lockunlock方法:

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

 

延迟锁

延迟锁,指的是在指定时间内没有获取到锁,就取消阻塞并返回获取锁失败,由调用线程自己决定后续操作,比如放弃操作或者创建轮询获取锁。

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        //调用AQS带延时功能获取方法
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

 

中断锁

tryLock(long timeout, TimeUnit unit)这个方法会抛出InterruptedException异常,可以用于实现中断锁,即等待的时间还未到,可以直接调用interrupt方法以中断获取锁。另外ReentrantLock还有一个方法可以实现中断锁,即:lockInterruptibly方法

public void lockInterruptibly() throws InterruptedException {
        //直接调用AQS的可中断获取方法
        sync.acquireInterruptibly(1);
    }

 

通过调用该方法获取锁跟lock方法一样,如果获取不到会阻塞,不同的是使用这个方法获取锁是可以在外部中断的,但lock方法不行。使用灵活使用可中断锁,可以防止死锁。

 

Ps:中断锁是对获取锁的中断,注意与线程被中断的区别(虽然本质上都是中断线程)。不管是使用显式锁还是内置锁的线程阻塞都是可以被中断的,而中断锁是指线程在获取锁的过程中被阻塞 可以中断现在继续排队获取锁的过程。中断锁:是中断获取锁排队阻塞,可以使用ReentrantLocktryLock(long timeout, TimeUnit unit)lockInterruptibly()这两个方法实现,而使用内置锁synchronized 如果线程已经在排队获取锁是无法被中断的;中断线程:是中断线程执行业务方法过程中的阻塞(如sleep阻塞,BlookingQueue阻塞)。

 

轮询锁

tryLock(long timeout, TimeUnit unit)这个方法延迟获取锁的方法,另外ReentrantLock还有一个非延迟也不阻塞的获取锁方法tryLock(),尝试获取锁,如果没有获取到直接反回false 不阻塞线程:

public boolean tryLock() {
        //默认只有非公平实现
        return sync.nonfairTryAcquire(1);
}

ReentrantLock不直接提供轮询锁api,但可以用tryLock(long timeout, TimeUnit unit)tryLock() 这两个方法实现。即没有获取到锁,可以使用while循环 隔一段时间再次获取,直到获取到为止,这种方式是解决死锁的常用手段。这两个方法的使用区别:tryLock(long timeout, TimeUnit unit)不用在whilesleep,而tryLock()需要自己在whilesleep一会儿,减少资源开销。以tryLock()为例 实现轮询锁:

public class ReentrantLockTest {
 
    public static void main(String[] args) throws Exception{
        ReentrantLock lock = new ReentrantLock(true);
        while (true){
            if(lock.tryLock()){//这里不阻塞
                try{
                    System.out.println("执行业务方法");
                    //业务方法
                    return;
                }catch (Exception e){
                    //业务异常
                }finally {
                    lock.unlock();
                }
 
            }
            //如果没有获取到睡一会儿,再取锁
            Thread.sleep(1000);
        }
    }
}

ReentrantLock中还有一个重要方法newCondition获取条件队列方法,其作用类似使用内置锁时ObjectwaitnotifynotifyAll。这部分内容比较多,在这里就不展开讲解,后面抽时间单独总结下。另外ReentrantLock还有一些其他辅助方法,都比较好理解,就不一一列举,在使用过程中自行查阅即可。

 

总结

 

 

最后再强调下显式锁Lock与内置锁是互补关系,有些场景下只能使用内置锁(比如对方法加锁);有些场景下只能使用Lock(比如 需要防止死锁、或者实现公平锁)。显式锁在java API中常用的还有读写锁ReentrantReadWriteLock,本次主要讲了重入锁ReentrantLock的实现原理和基本用法。

 

 

0
0
分享到:
评论

相关推荐

    Java并发锁简介-动力节点共9页.pdf.zip

    1. ** monitors**: Java中的每个对象都有一个与之关联的监视器锁,当一个线程进入synchronized代码块或方法时,会自动获取该锁,其他线程尝试进入时会被阻塞,直到锁被释放。 2. ** 同步代码块**: 使用`synchronized...

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    Java中的ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个高级锁,它是可重入的,意味着一个线程可以多次获取同一锁。在深入ReentrantLock之前,我们首先需要了解Java并发编程的基础,特别是Java...

    深入理解java内置锁(synchronized)和显式锁(ReentrantLock)

    深入理解Java内置锁(synchronized)和显式锁(ReentrantLock) Java中有两种锁机制:内置锁(synchronized)和显式锁(ReentrantLock)。内置锁是Java语言提供的一种同步机制,使用synchronized关键字声明的方法或...

    笔记-4、显式锁和AQS1

    在Java并发编程中,显式锁(Lock)和隐式锁(synchronized)是两种常见的锁机制,它们提供了对共享资源的互斥访问。显式锁通过Java的Lock接口实现,而隐式锁主要依赖于synchronized关键字。 **Lock接口和...

    笔记-4、显式锁和AQS(1)1

    为了提供更灵活的锁控制,Java引入了Lock接口,它是显式锁的代表,允许我们执行更复杂的操作,如中断锁获取、超时获取和尝试获取等。ReentrantLock是Lock接口的一个具体实现,它支持可重入性,即一个线程可以多次...

    Java并发显式锁和显式条件队列

    **显式锁**,主要指的是`java.util.concurrent.locks.Lock`接口及其实现,如`ReentrantLock`。与`synchronized`关键字不同,显式锁的加锁和解锁是显式的,而不是隐含在方法或代码块的进入和退出。`ReentrantLock`...

    java锁的释放与建立

    显式锁则是通过`java.util.concurrent.locks.Lock`接口及其实现类如`ReentrantLock`来实现,提供更细粒度的控制。 锁的释放与建立过程至关重要,因为它们直接影响到线程间的同步行为。在Java内存模型中,_happens-...

    Java并发编程实践-07章-显示锁1

    `Java`的`Lock`接口和`ReentrantLock`类提供了比内置锁更强大的并发控制能力,支持中断、超时等待和可轮询的锁请求,以及更灵活的`Condition`管理。在设计高性能并发系统时,理解并正确使用这些工具是至关重要的。...

    面向Java锁机制的字节码自动重构框架.zip

    显式锁是Java 5引入的java.util.concurrent.locks包中的Lock接口及其实现类,如ReentrantLock。与内置锁相比,显式锁提供了更多的控制选项,例如非公平锁、可中断锁、定时锁等。显式锁需要程序员手动获取和释放,这...

    java-syn.zip_Java syn_Java syn锁_java同步锁syn_java锁 syn_syn同步事务锁

    - **显式锁**:如`java.util.concurrent.locks.Lock`接口及其实现,如`ReentrantLock`,提供了更复杂的锁操作,如可中断锁等待、公平锁等。 - **读写锁**:`ReentrantReadWriteLock`允许多个读取者同时访问,但...

    2018年蚂蚁课堂(每特教育)-Java工程师面试宝典-V1.0

    解决线程安全问题的方法主要有两种:使用`synchronized`关键字实现同步或使用显式锁(如`ReentrantLock`)。 - 使用`synchronized`关键字:可以修饰方法或者代码块,确保同一时刻只有一个线程可以执行被同步的代码...

    java经典面试题目-面经-java-Java语言的进阶概念-常用的库和框架-并发编程-网络编程-Web开发-面经

    Java的锁机制包括内置锁(synchronized)、显式锁(Lock接口)、读写锁(ReadWriteLock)等,适用于不同的同步需求。同步处理线程间的共享数据,而异步则让任务并发执行,等待结果。 AOP(面向切面编程)用于分离...

    简单聊聊Synchronized和ReentrantLock锁.docx

    相比之下,ReentrantLock(可重入锁)是Java并发包java.util.concurrent.locks中的一个类,提供了更细粒度的锁控制。ReentrantLock允许显式获取和释放锁,并且支持更丰富的锁原语,如公平锁、非公平锁、可中断锁、...

    Java并发编程之显式锁机制详解

    Java并发编程中的显式锁机制是比`synchronized`关键字更为灵活的一种同步控制方式。显式锁允许程序员更精细地控制锁的获取与释放,从而在某些复杂场景下提供更高的性能和更多的功能。本文将深入探讨Java中Lock接口...

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    根据给定文件的信息,我们可以深入理解AQS(AbstractQueuedSynchronizer)独占锁之ReentrantLock的源码分析及其实现原理。这不仅包括ReentrantLock本身的特性,还包括了其背后的AQS框架是如何工作的。 ### 一、管程...

    提升Java的锁性能Java开发Java经验技巧共5页.p

    - **显式锁(Lock)**:Java并发包`java.util.concurrent.locks`提供了显式锁,如`ReentrantLock`,相比内置锁,它提供了更细粒度的控制,如可中断、公平性等特性。 2. **使用并发容器** - **ConcurrentHashMap**...

Global site tag (gtag.js) - Google Analytics