关注微信公众号:CodingTechWork,一起学习交流进步。

引言

  重入锁,顾名思义在于这个字。开发过程中,我们在用到锁时,可能会用于递归的方法上加锁,此时,那同一个方法对象去重复加锁,是怎么加的呢?本文一起学习一下重入锁这个概念。

重入锁介绍

重入锁概念

  重入锁ReentrantLock,是指支持重进入的锁,表示锁可以支持一个线程对资源的重复加锁,也就是说任意线程在获取到这个锁之后,如果说再次获取该锁时,不会被锁所阻塞(递归无阻塞)。另外,重入锁还支持锁时的公平非公平性(默认)选择。

重入锁实现

  实现重入机制,必须解决两个问题:1)线程需要再次获取锁;2)锁需要得到最终的释放。

  1. 线程再次获取锁
    锁一定要能够识别获取锁的线程是否为当前占据锁的线程,如果是,则获取成功。
  2. 锁的最终释放
    锁获取对应就会有锁释放,线程重复n次获取了该锁后,需要在第n次释放锁后,其他线程才能够获取该锁。实现机制是计数器:锁每获取一次,计数器自增1,该计数表示当前锁被重复获取的次数;锁每释放一次,计数器自减1,一直减到0,表示当前线程已成功释放该锁,其他线程可以来获取该锁。

公平锁和非公平锁

  对于锁的获取,会存在公平性的问题。
  所谓公平锁,其实就是先对锁进行获取的请求肯定优先进行的,锁获取的顺序符合请求的绝对时间顺序,类似于FIFO。反之,即为非公平性锁。

ReentrantLock源码分析

    /** Synchronizer providing all implementation mechanics */private final Sync sync;/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}/*** Sync object for fair locks*/static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/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;}}/*** Sync object for non-fair locks*/static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}/*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

ReentrantLock的重入性和公平性

  ReentrantLock不支持隐式的重入锁,但是可以在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁且不被阻塞。
  从ReentrantLock源码中,我们可以看到上述的一个构造方法,ReentrantLock的公平与否,正是通过构造方法来抉择的,内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。如果在绝对的时间上,对锁先发出获取请求的线程一定是先被满足的,这个锁即为公平的,其实也就是等待时间最长的线程最优先获取该锁,所以公平锁的获取是顺序的。
  公平的锁机制通常没有非公平的效率高,但是公平锁可以减少“饥饿”发生的概率,等待越久的请求越是可以得到最先满足,不会导致一个线程苦苦等了长时间后得不到满足。

ReentrantLock非公平性锁和公平性锁

 ....../*** The synchronization state.*/private volatile int state;/*** Returns the current value of synchronization state.* This operation has memory semantics of a {@code volatile} read.* @return current state value*/protected final int getState() {return state;}/*** The current owner of exclusive mode synchronization.*/private transient Thread exclusiveOwnerThread;/*** Sets the thread that currently owns exclusive access.* A {@code null} argument indicates that no thread owns access.* This method does not otherwise impose any synchronization or* {@code volatile} field accesses.* @param thread the owner thread*/protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}......

非公平nonfairTryAcquire()源码

 ....../*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/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) // overflowthrow new Error("Maximum lock count exceeded");//设置锁的计数值,并返回truesetState(nextc);return true;}//其他线程,返回falsereturn false;}......

  非公平锁是ReentrantLock默认的锁方式,如何获取同步状态?如上述的源码中显示该方法增加了再次获取同步状态的处理逻辑是通过判断当前线程是否为获取锁的线程,从而决定获取锁的操作是true还是false,如果说是获取锁的线程发出的请求,则同步状态值会自增1并返回true,表示获取锁成功;若不是获取锁的线程发出的请求 ,则返回false。只要CAS设置同步状态值成功,就表示当前线程获取了锁。

公平tryAcquire()源码

 ......public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}....../*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/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;}......

  公平锁同步计数与非公平锁的区别在于:公平锁的方法多了一个hasQueuedPredecessors()方法判断,判断同步队列中当前节点是否有前驱节点,如果有,则表示有线程比当前线程更早地发出了获取锁的请求,所以需要等待更早的线程获取并释放锁后才能去获取该锁。

tryRelease()源码

 ......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;//将该锁的独占线程对象置为null,其他线程可以来获取锁。setExclusiveOwnerThread(null);}//设置同步状态值setState(c);return free;}......

  同步状态值在再次获取锁时,自增1,对应的,当释放锁是会递减1。上述源码中,可以看到如果该锁被获取了n次,那么前(n-1)次的tryRelease()方法必定是返回了false,只有当第n次完全释放锁,同步状态值c==0,此时独占锁的线程对象也被设置为了null,才会返回true,表示完全释放锁成功。

测试

package com.example.andya.demo.service;import javafx.concurrent.Task;import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author Andya* @date 2020/12/6*/
public class FairAndUnfairLockTest {//公平锁private static Lock fairLock = new ReentrantLockDemo(true);//非公平锁private static Lock unfairLock = new ReentrantLockDemo(false);//公平锁线程public void lockFairTask() {TaskThread taskThread = new TaskThread(fairLock, "FAIR");taskThread.start();}//非公平锁线程public void lockUnfairTask() {TaskThread taskThread = new TaskThread(unfairLock, "UNFAIR");taskThread.start();}//线程启动后打印线程信息private static class TaskThread extends Thread{private Lock lock;private String type;public TaskThread(Lock lock, String type) {this.lock = lock;this.type = type;}@Overridepublic void run() {for (int i = 0; i < 2; i++) {lock.lock();try {System.out.println(type + " lock by [" + getId() + "], waiting by "+ ((ReentrantLockDemo)lock).getQueuedThreads());} finally {lock.unlock();}}}//重写toString方法,使得线程打印出线程id来标识线程@Overridepublic String toString() {return getId() + "";}}private static class ReentrantLockDemo extends ReentrantLock {public ReentrantLockDemo(boolean fair) {super(fair);}//获取正在等待获取锁的线程列表@Overridepublic Collection<Thread> getQueuedThreads() {List<Thread> threadList = new ArrayList<>(super.getQueuedThreads());Collections.reverse(threadList);return threadList;}}public static void main(String[] args) {FairAndUnfairLockTest fairAndUnfairLockTest = new FairAndUnfairLockTest();for (int i = 0; i < 5; i++) {//公平锁测试fairAndUnfairLockTest.lockFairTask();//非公平锁测试
//            fairAndUnfairLockTest.lockUnfairTask();}}
}

结果

// 公平锁的结果
FAIR lock by [9], waiting by []
FAIR lock by [10], waiting by [11, 12, 9]
FAIR lock by [11], waiting by [12, 9, 13, 10]
FAIR lock by [12], waiting by [9, 13, 10, 11]
FAIR lock by [9], waiting by [13, 10, 11, 12]
FAIR lock by [13], waiting by [10, 11, 12]
FAIR lock by [10], waiting by [11, 12, 13]
FAIR lock by [11], waiting by [12, 13]
FAIR lock by [12], waiting by [13]
FAIR lock by [13], waiting by []// 非公平锁的结果
UNFAIR lock by [10], waiting by [9, 11]
UNFAIR lock by [10], waiting by [9, 11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [13], waiting by []
UNFAIR lock by [13], waiting by []

分析

  从结果中我们可以看出来,公平锁每次都是从同步队列中的第一个节点获取锁,而非公平锁则是会连续两次获取锁。
  刚释放锁的线程再次获取同步状态的概率比较大,会出现连续获取锁的情况。
  同时,我们可以看到另一个问题就是开销,对于公平锁的开销会比较大一些,因为它每次都是切换到另一个线程,而对于非公平锁,会出现连续获取锁的现象,切换次数少一些,所以非公平性锁的开销会更小一些。这也反映了公平锁虽然保证了锁的获取按照顺序进行,保证了公平性,解决了“饥饿”问题,但是代价却是进行了大量的线程切换

JAVA——以ReentrantLock为例学习重入锁以及公平性问题相关推荐

  1. 详解ReentrantLock为什么是可重入锁

    1 缘起 有一次,公司有人在面试,路过时,听到面试官问到了锁, 让面试者聊一聊用到的锁, 我此时,也是心里一震, 我用过哪些锁?为什么使用? 搜索了好一会儿,哈哈哈,我就是这么菜. 只学习过synch ...

  2. java lock可重入_Java源码解析之可重入锁ReentrantLock

    本文基于jdk1.8进行分析. ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock. 首先看一下源码中对ReentrantLock的介绍. ...

  3. Java 并发编程之可重入锁 ReentrantLock

    Java 提供了另外一个可重入锁,ReentrantLock,位于 java.util.concurrent.locks 包下,可以替代 synchronized,并且提供了比 synchronize ...

  4. 闲聊AQS面试和源码解读---可重入锁、LockSupport、CAS;从ReentrantLock源码来看公平锁与非公平锁、AQS到底是怎么用CLH队列来排队的?

    AQS原理可谓是JUC面试中的重灾区之一,今天我们就来一起看看AQS到底是什么? 这里我先整理了一些JUC面试最常问的问题? 1.Synchronized 相关问题以及可重入锁 ReentrantLo ...

  5. Java成神之路——重入锁、公平非公平锁、自旋锁、读写锁

    你知道的Java锁有哪些? synchronized?Lock?它们又有什么区别?锁可分为哪些种类?锁是如何实现的? 公平与非公平锁 公平锁与非公平锁的区别体现在锁造成阻塞时的排队机制,公平锁按申请锁 ...

  6. 【java】动态高并发时为什么推荐重入锁而不是Synchronized?

    1.概述 转载:http://www.dreamwu.com/post-1758.html 这个图画的不错,有助于加深理解. [Java]Synchronized 有几种用法 [java] 从hots ...

  7. JAVA可重入锁死锁

    可重入锁 可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的. synchronized 和 ReentrantLock 都是可重入锁. ...

  8. Java锁——可重入锁(递归锁)

    概念 指在同一个线程外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞.即一个线程中的多个流程可以获取同一把锁,持有这把同步锁 ...

  9. Java 中可重入锁、不可重入锁的测试

    可重入锁 指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁. 为了避免死锁的发生,JDK 中基本都是可重入锁. 下面我们来测试一下 synchronized 和  java.util.c ...

最新文章

  1. 0.5mm的焊锡丝能吃多大电流_BTB/FPC大电流弹片微针模组高度满足FPC连接器测试需求...
  2. Matlab中自定义函数(一)
  3. JZOJ 4161. 于神之怒
  4. string[x]:size 属性具有无效大小值0
  5. 关于sklearn中“决策树是否可以转化为json并进行绘制”的调研
  6. linux磁盘资源,liunxCPU和内存,磁盘等资源,
  7. Google我的商家设定
  8. yum安装 vs 源码编译安装
  9. 玩转SpringBoot2.x之缓存对象
  10. 使用os.system调用外部程序,如wget下载
  11. java获取redis中各种数据类型key对应的value代码简单封装
  12. Qt学习之路 (一)概述
  13. windows卸载服务
  14. 使用接口接收json数据
  15. 有道无术,术尚可求;有术无道,止于术!
  16. learn git branching学习整理
  17. IT服务外包的必要性
  18. deepin efi 启动u盘_deepin启动引导修复教程
  19. 固态硬盘跟机械硬盘是怎么储存数据
  20. 多通道声源定位方法之GCC-PHAT:原理及matlab实现

热门文章

  1. python编程基础张勇答案_Python程序开发、编程基础阶段试题及答案
  2. 编译C/C++为dll供Python调用
  3. linux那些事之pin memory相关API
  4. 笨方法“学习python笔记之条件控制
  5. request.form以及postman发送表单数据
  6. 叙述计算机网络拓扑结构的定义,计算机网络拓扑结构的定义
  7. python安装方法及运行_Python下载及其安装步骤
  8. Linux版APP超级签名分发系统源码
  9. 高并发处理方案_高并发系统下的缓存解决方案
  10. Java | 用Java实现冒泡排序算法