• ReentrantLock和AQS


     

      AQS(AbstractQueuedSynchronizer)是JDK1.5提供的一个用来构建锁和同步工具的框架,子类包括常用的ReentrantLock、CountDownLatch、Semaphore等。

      AQS没有锁之类的概念,它有个state变量,是个int类型 ,state 是同步状态位,具体是否能够获取锁就是通过修改state来实现

      AQS的功能可以分为独占和共享,ReentrantLock实现了独占功能

    ReentrantLock锁的架构:

      ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。

    获取锁的过程:

      线程去竞争一个锁,可能成功也可能失败。成功就直接持有资源,不需要进入队列;失败的话进入队列阻塞,等待唤醒后再尝试竞争锁。

    公平锁尝试获取锁:

     1 final void lock() { acquire(1);}
     2 
     3 public final void acquire(int arg) {
     4     if (!tryAcquire(arg) &&
     5         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     6         selfInterrupt();
     7 }
     8 
     9 protected final boolean tryAcquire(int acquires) {
    10    final Thread current = Thread.currentThread();
    11    int c = getState();
    12    if (c == 0) {
    13        if (!hasQueuedPredecessors() &&
    14            compareAndSetState(0, acquires)) {
    15            setExclusiveOwnerThread(current);
    16            return true;
    17        }
    18    }
    19    else if (current == getExclusiveOwnerThread()) {
    20        int nextc = c + acquires;
    21        if (nextc < 0)
    22            throw new Error("Maximum lock count exceeded");
    23        setState(nextc);
    24        return true;
    25    }
    26    return false;
    27 }

      第一个if判断AQS的state是否等于0,表示锁没有人占有。

      接着,hasQueuedPredecessors判断队列是否有排在前面的线程在等待锁,没有的话调用compareAndSetState使用cas的方式修改state,将0改为1。

      最后线程获取锁成功,setExclusiveOwnerThread将线程记录为独占锁的线程。

      第二个if判断当前线程是否为独占锁的线程,因为ReentrantLock是可重入的,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到state为零。

      如果最后获取锁失败,下一步需要将线程加入到等待队列。

    线程进入等待队列:

      AQS内部有一条双向队列存放等待线程,节点是Node对象。每个Node维护了线程、前后Node的指针和等待状态等参数。

      线程在加入队列之前,需要包装进Node,调用方法是addWaiter。

      每个Node需要标记是独占的还是共享的,由传入的mode决定,ReentrantLock自然是使用独占模式Node.EXCLUSIVE。

      创建好Node后,如果队列不为空,使用cas的方式将Node加入到队列尾。注意,这里只执行了一次修改操作,并且可能因为并发的原因失败。因此修改失败的情况和队列为空的情况,需要进入enq()方法。

    阻塞等待线程:

      线程加入队列后,下一步是调用acquireQueued阻塞线程。

    非公平锁获取锁:

    1 final void lock() {
    2     if (compareAndSetState(0, 1))
    3         setExclusiveOwnerThread(Thread.currentThread());
    4     else
    5         acquire(1);
    6 }

      在NonfairSync的lock方法里,第一步直接尝试将state修改为1,很明显,这是抢先获取锁的过程。如果修改state失败,则和公平锁一样,调用acquire。

      公平锁会关注队列里排队的情况,老老实实按照FIFO的次序;非公平锁只要有机会就抢占,才不管排队的事。

    羊群效应:

      当有多个线程去竞争同一个锁的时候,假设锁被某个线程占用,那么如果有成千上万个线程在等待锁,有一种做法是同时唤醒这成千上万个线程去去竞争锁,这个时候就发生了羊群效应,海量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。

      AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。其实这个思路已经被应用到了分布式锁的实践中,见:Zookeeper分布式锁的改进实现方案。







  • 相关阅读:
    mongodb 数据库操作--备份 还原 导出 导入
    括号匹配算法求解(用栈实现)
    最短路径(图中两点间最短路径)
    城市之间的最短总距离(最小生成树算法)
    简单的约瑟夫环算法
    动态数组排序实例
    折半查找算法
    对字符串进行快速排序(即字符数组排序)
    字符串数组排序的快速排序实现
    插入排序反序排序
  • 原文地址:https://www.cnblogs.com/mengchunchen/p/9250682.html
Copyright © 2020-2023  润新知