写点什么

从源码分析可重入锁(ReentrantLock)

  • 2023-05-20
    湖南
  • 本文字数:2210 字

    阅读完需:约 7 分钟

重入锁(ReentrantLock),从它的名字就可以看得出来,它是指任意线程在获取到锁之后能够再次获取该锁而不被阻塞。在非重入锁的情况下一个线程获取到的锁,再次去获取锁的时候,那么这个线程就会因为之前获取到的锁而阻塞。


我们所熟知的 synchronized 关键字隐式地支持重进入,执行线程在获取了锁之后仍能多次获取锁,而 ReentrantLock 是显式地支持重入,获得锁的状态下,再次调用 Lock()方法获取锁而不被阻塞。隐式锁和显式锁的区别在 显示锁 Lock 有介绍。

如何实现重进入?

为实现重进入锁我们需要解决两个问题:

  1. 线程是否可以再次获取锁:线程需要去识别获取锁的线程是否是当前占据锁的线程,如果是可以再次获取,如果不是,那肯定不能(是我的锁我怎么锁都没问题,不是我的锁一次也不能用!)

  2. 锁的释放:获取了 n 次锁,那肯定要释放 n 次锁才能让其他线程获取锁。这就要求对于锁的获取进行计数自增,锁被释放时要求计数自减,当计数为 0 时表示锁被成功释放。


ReentrantLock 通过组合自定义同步器来实现锁的获取与释放,获取同步状态的源代码如下:

    final boolean nonfairTryAcquire(int acquires) {           final Thread current = Thread.currentThread();           int c = getState();           if (c == 0) {               if (compareAndSetState(0, acquires)) {                   setExclusiveOwnerThread(current);                   return true;              }          }           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;      }
复制代码

该方法是通过判断当前线程是否为获取锁的线程来决定操作是否成功。


成功获取锁的线程再次获取锁就是增加同步状态值,那么在释放锁时就要减少同步状态值。释放同步状态方法源代码如下:

 protected final boolean tryRelease(int releases) {           int c = getState() - releases;           if (Thread.currentThread() != getExclusiveOwnerThread())               throw new IllegalMonitorStateException();           boolean free = false;           if (c == 0) {               free = true;               setExclusiveOwnerThread(null);          }           setState(c);           return free;      }
复制代码

从 tryRelease 方法可以看出,只有 c==0 时,即同步状态为 0 时将占有线程设置为 null,并返回 true,表示释放成功。


这里还涉及到了锁获取的公平性问题,就像我们平时去食堂排队买饭,肯定是先排队的人先买到饭,不然干饭人可要掀桌子了。可我们看看上面的 nonfairTryAcquire 方法,只要设置同步状态成功就算该线程获取到了锁,并不保证先请求先获得锁,这可一点不公平。

公平锁与非公平锁的区别

公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是先进先出


与默认的 nonfairTryAcquire 方法不同,ReentrantLock 的 tryAcquire 方法则可实现公平锁。该方法源代码如下:

       protected final boolean tryAcquire(int acquires) {           final Thread current = Thread.currentThread();           int c = getState();           if (c == 0) {               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;      }  }
复制代码

可以发现 tryAcquire 与之前的 nonfairTryAcquire 代码大致是一样的,唯一不同的点就是该方法在判断条件上多了一个 hasQueuedPredecessors,意思是判断加入同步队列中当前节点是否有前驱节点,如果有则表示有比当前线程更早请求锁的线程,只能等待前面的线程获取到并释放锁之后才能继续获取锁,可不能插队哦,咱们可是要讲文明的嘞。


但公平性锁一定就比非公平锁好吗?其实不然,公平性锁保证了锁的获取按照 first in first out 的特征,但代价就是需要进行大量的线程切换,非公平性锁可能导致一个线程请求了老长时间,可能运气差就是抽不到它去获取锁,但是非公平性锁极少的线程切换,保证了更好的性能和吞吐量。


作者:周天天

链接:https://juejin.cn/post/7234541707332010039

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
从源码分析可重入锁(ReentrantLock)_Java_做梦都在改BUG_InfoQ写作社区