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的判断

  • 相关阅读:
    图像检索(image retrieval)- 11
    图像检索(image retrieval)- 10相关
    Mock.js简易教程,脱离后端独立开发,实现增删改查功能
    Azure Monitor (3) 对虚拟机磁盘设置自定义监控
    Azure Monitor (1) 概述
    Azure SQL Managed Instance (2) 备份SQL MI
    Azure Virtual Network (17) Private Link演示
    Azure Virtual Network (16) Private Link
    Azure Virtual Network (15) Service Endpoint演示
    Azure Virtual Network (14) Service Endpoint服务终结点
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12166629.html
Copyright © 2011-2022 走看看