zoukankan      html  css  js  c++  java
  • ReentrantLock原理分析

    概述

      ReentrantLock是基于AQS独占模式实现的一种可重入锁,与synchronized不同的是,ReentrantLock提供了公平锁和非公平锁的选择。其本质是基于操作系统的管程实现的。本文就分析一下ReentrantLock的实现原理,由于AQS在AQS-独占模式分析已经介绍过,所以涉及到AQS的地方不会过多介绍,本文会涉及到源码,文章会比较臃肿。

    ReentrantLock整体结构

                    

     从上图可以看出:

    • ReentrantLock实现了Lock接口
    • ReentrantLock内部是基于Sync这个抽象类实现的,而Sync继承了AQS,重写了部分AQS的方法
    • Sync有两个继承类,FairSync是实现公平锁的,而NonFairSync是实现非公平锁的

    下面就分析一下每一块的代码

    Sync抽象类

     1 //继承AQS
     2 abstract static class Sync extends AbstractQueuedSynchronizer {
     3         private static final long serialVersionUID = -5179523762034025860L;
     4      //子类实现加锁方法,因为有公平锁和非公平锁,所以这里没有实现
     5         abstract void lock();
     6      //该方法时tryLock方法调用的,tryLock方法是尝试获取锁,如果没有获取成功,就直接返回
     7         //不会阻塞,非公平的方式,直接竞争锁
     8         final boolean nonfairTryAcquire(int acquires) {
     9             final Thread current = Thread.currentThread();
    10             int c = getState();
    11             //该state是AQS中的,如果为0表示没有别的线程占有锁
    12             if (c == 0) {
    13                 //通过CAS加锁
    14                 if (compareAndSetState(0, acquires)) {
    15                     //加锁成功,将持有锁的线程改成当前线程
    16                     setExclusiveOwnerThread(current);
    17                     return true;
    18                 }
    19             }
    20             //如果持有锁的线程就是当前线程,就是可重入锁,直接修改状态
    21             else if (current == getExclusiveOwnerThread()) {
    22                 int nextc = c + acquires;
    23                 if (nextc < 0) // overflow
    24                     throw new Error("Maximum lock count exceeded");
    25                 setState(nextc);
    26                 return true;
    27             }
    28             //否则加锁失败,直接返回
    29             return false;
    30         }
    31      //释放锁
    32         protected final boolean tryRelease(int releases) {
    33             int c = getState() - releases;
    34             if (Thread.currentThread() != getExclusiveOwnerThread())
    35                 throw new IllegalMonitorStateException();
    36             boolean free = false;
    37             if (c == 0) {
    38                 free = true;
    39                 setExclusiveOwnerThread(null);
    40             }
    41             setState(c);
    42             return free;
    43         }
    44         //判断当前线程是不是持有锁的线程
    45         protected final boolean isHeldExclusively() {
    46             // While we must in general read state before owner,
    47             // we don't need to do so to check if current thread is owner
    48             return getExclusiveOwnerThread() == Thread.currentThread();
    49         }
    50         //在AQS中的创建ConditionObject对象,保存等待线程
    51         final ConditionObject newCondition() {
    52             return new ConditionObject();
    53         }
    54 
    55         final Thread getOwner() {
    56             return getState() == 0 ? null : getExclusiveOwnerThread();
    57         }
    58 
    59         final int getHoldCount() {
    60             return isHeldExclusively() ? getState() : 0;
    61         }
    62  
    63         final boolean isLocked() {
    64             return getState() != 0;
    65         }
    66 
    67         private void readObject(java.io.ObjectInputStream s)
    68             throws java.io.IOException, ClassNotFoundException {
    69             s.defaultReadObject();
    70             setState(0); // reset to unlocked state
    71         }
    72     }

    Sync抽象类总结

    • 该类主要实现了一个非公平的方式尝试获取锁,如果成功,ok,如果失败,直接返回,不会阻塞或者空转等待
    • 实现了一个释放锁的方法,该方法是公平锁和非公平锁公用的方法
    • 剩下的都是一些判断的方法,很简单 

    NonFairSync类分析

     1  static final class NonfairSync extends Sync {
     2         private static final long serialVersionUID = 7316153563782823691L;
     3         //实现加锁方法,ReentrantLock默认就是这个方法,是一个非公平锁
     4         final void lock() {
     5             if (compareAndSetState(0, 1))
     6                 setExclusiveOwnerThread(Thread.currentThread());
     7             else
     8                 acquire(1);
     9         }
    10         //这里就是调用上面Sync类中尝试获取锁的方法
    11         protected final boolean tryAcquire(int acquires) {
    12             return nonfairTryAcquire(acquires);
    13         }
    14     }

    FairSync类分析

    static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
            //公平锁获取锁的实现和上面就不一样,这里没有通过CAS尝试获取锁,而是直接调用acquire方法
            final void lock() {
                acquire(1);
            }
            //尝试获取锁
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    //这里就是和非公平锁不同的地方,这里调用了hasQueuedPredecessors,后面会分析这个方法
                    //剩下的和公平锁的实现就是一样的
                    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;
            }
        }

    进入AbstractQueuedSynchronizer #hasQueuedPredecessors()方法

     1   public final boolean hasQueuedPredecessors() {
     2          Node t = tail; // Read fields in reverse initialization order
     3          Node h = head;
     4          Node s;
     5           //当前队列中不只有一个节点
     6           //判断第二个节点是否为空,这个主要是为了防止第三个条件发生空指针异常,因为第一个条件虽然判断了队列中有第二个节点
     7           //但是由于是在多线程环境下,随时都有可能第一个节点被干掉,第二个节点变成第一个节点,然后第二个就是null了,再之后就会发生空
     8           //指针异常,这个返回的主要是为了判断当前线程是不是队列中的第二个节点,因为队列的第一个节点是虚拟节点,第二个节点才有获取锁的资格
     9           //在公平锁的情况下是这样,但是非公平锁就不是这样了
    10          return h != t &&
    11              ((s = h.next) == null || s.thread != Thread.currentThread());
    12      }

    总结:公平锁的实现就是严格按照入队的先后顺序获取锁。

    Lock接口

    public interface Lock {
    
        void lock();
        //获取可中断锁
        void lockInterruptibly() throws InterruptedException;
        //尝试获取锁
        boolean tryLock();
        //try获取锁,设置锁最大等待时间,如果超时就取消获取
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
       
        void unlock();
    
        Condition newCondition();
    }
    

    ReentrantLock构造方法

    public ReentrantLock() {
            sync = new NonfairSync();
        }
    //根据传入参数不同,初始化不同的类
    public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

    ReentrantLock和Synchronized对比

    • 与 synchronized 相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候
    • ReentrantLock 还提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合。
    • ReentrantLock 支持中断处理,且性能较 synchronized 会好些。
    • ReentrantLock实现了公平锁和非公平锁的机制,而synchronized只有非公平一种模式
    • synchronized不需要手动释放锁,但是ReentrantLock释放锁的时候,需要程序员自己释放
    • ReentrantLock和synchronized都是通过内存屏障来保证有序性、可见性的,只不过ReentrantLock是volatile实现的,而synchronized不是使用volatile,但是volatile和synchronized在保证有序性和可见性上使用的内存屏障是一样的,所以也就是说两者基本相同

    总结

    本文介绍ReentrantLock源码结构以及实现原理,涉及到AQS的部分没有详细说明,主要是另一个篇文章已经介绍过,最后介绍了ReentrantLock和sychronized的区别。

    参考:

    【死磕 Java 并发】—– J.U.C 之重入锁:ReentrantLock

      

  • 相关阅读:
    Ubuntu下启用IPV6
    时间倒计时(天数+时+分+秒)
    移动端触摸移动相互调换位置
    js移动端点击隐藏和出现指定div(包含少许特效)
    将页面内容保存为图片显示,长按保存至本地(html2canvas)
    移动端触摸上拉隐藏指定模块内容,有过度效果(同时页面iscroll滚动)
    移动端图片缩放(包括滑动查看)
    移动端图片缩放(不包括滑动查看)
    IOS返回页面不刷新的问题
    datePicker(可以同时选择日期和时间)
  • 原文地址:https://www.cnblogs.com/gunduzi/p/13615441.html
Copyright © 2011-2022 走看看