zoukankan      html  css  js  c++  java
  • ReentrantLock 重入锁

    解决了什么问题

    重入锁解决了同步方法调用另一个同步方法时死锁的问题(即方法A没有解锁的情况下 方法B可以取得锁 并在B归还锁之后 锁依然被A持有)

    代码示例:

    以下代码中使用两种重入方式  关键字synchronized 和 基于AQS的重入锁ReentrantLock 

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.testA();
        test.testC();
    
    }
    
    //inner class
    public static class Test{
    
        ReentrantLock lock = new ReentrantLock();
    
        //使用synchronized重入锁
        public synchronized void testA(){
            System.out.println("testA");
            testB();
        }
        private synchronized void testB(){
            System.out.println("testB");
        }
    
        //使用ReentrantLock重入锁
        public void testC(){
            lock.lock();
            System.out.println("testC");
            testD();
            lock.unlock();
        }
        private void testD(){
            lock.lock();
            System.out.println("testD");
            lock.unlock();
        }
    
    }

    代码分析:

    test.testA() 使用synchronized 关键字, 进入方法可知, testA方法取得了test的对象锁(不太明白可以看我之前synchronized 文章), 然后在方法testA中调用testB方法, 此时对象锁未被释放, 但是synchronized 属于重入锁, 同一个线程中的testB依然可以获取test对象锁

    test.testC() 使用ReentrantLock锁,同样达到同一线程重入的目的, 具体源码解析接下来讲

    ReentrantLock源码分析:

    lock()方法: 获取锁, 原理比较简单, 若没有线程占有锁则之间独占; 再判断是否有线程占有锁则判断是不是当前线程占有了, 若是当前线程占有则, 将AQS的state加1, 若非当前线程则假如AQS等待队列 

    类 ReentrantLock
    //获取锁
    public void lock() {
        sync.lock();
    }
    
    类 ReentrantLock.NonfairSync
    final void lock() {
        //尝试原子操作获取锁 这是非公平锁特性 先尝试获取锁 获取不到再做判断
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        //没有获取到锁
        else
            acquire(1);
    }
    //获取锁
    public final void acquire(int arg) {
        //若是当前线程获取锁 tryAcquire(arg)=true则直接走完代码
        //若非当前线程获取锁 tryAcquire(arg)=false 则会acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 将当前线程假如等待锁队列
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //尝试获取锁
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    //非公平锁尝试获取实现
    final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取当前AQS状态
        int c = getState();
        //若当前状态为0 表示没有线程占有锁 则原子操作获取锁
        //并将AQS独占线程设置为当前线程
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                //AQS独占线程设置为当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //如果c!=0,即有线程获取了锁 则判断获取到锁的线程是否为当前线程
        else if (current == getExclusiveOwnerThread()) {
            //预期设置AQS的state=state+acquires 在这里acquires值为1
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //设置AQS状态为state=state+acquires
            setState(nextc);
            //返回获取锁成功
            return true;
        }
        return false;
    }

    unlock方法: 释放锁, 原理是利用AQS的state和exclusiveOwnerThread来判断, 若非当前线程释放锁直接抛出异常;  若是当前线程操作则需要对比state, 通俗的说就是在同一个线程中每一次lock()方法则state加1 每一次unlock()方法state减1

    知道unlock()次数和lock()次数一样, state的值重新变成0, 则释放锁, 并将锁让给下一个等待中的线程

    类 ReentrantLock
    //释放锁
    public void unlock() {
            sync.release(1);
        }
    
    类 ReentrantLock.Sync
    
    //释放锁
    public final boolean release(int arg) {
        //tryRelease(arg)解释如下
        //若是当前线程释放锁, 且AQS状态state等于0,tryRelease(arg)等于true 则唤醒当前线程,并通知后面等待线程
        //若是当前线程释放锁, 且AQS状态state不等于0,tryRelease(arg)等于false 则只是将state减1
        //若是非当前线程释放 抛出异常
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    protected final boolean tryRelease(int releases) {
        //c=state-1
        int c = getState() - releases;
        //若当前释放锁的线程非独占此锁的线程 则抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
    
        //如果state-1==0 则释放成功, 并设置独占线程为空, 以便下一个线程独占此锁
        //若是重入锁 那么state-1!=0 则独占线程任然为当前线程 其他线程依然无法获取锁
        if (c == 0) {
            free = true;
            //设置当前独占线程为空
            setExclusiveOwnerThread(null);
        }
        //设置当前state=c=state-1
        setState(c);
        //若是重入 c!=0 则free任然是false
        return free;
    }

    总结:作为AQS的高级应用, 实现上并不复杂, 只是在AQS的基础上做了state的判断

  • 相关阅读:
    JAVA实现DES加密实现详解
    CentOS 7安装Hadoop 3.0.0
    使用JAVA开发微信公众平台(一)——环境搭建与开发接入
    Oracle触发器用法实例详解
    负载均衡中使用 Redis 实现共享 Session
    在windows上部署使用Redis
    java 线程排查问题流程
    使用Fernflower 比较准确的反编译整个java项目
    Mysql用户本机登陆不成功的解决
    kali 系统的源
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12166629.html
Copyright © 2011-2022 走看看