首页 > 其他分享 >可重入锁ReentrantLock在性能测试常见用法

可重入锁ReentrantLock在性能测试常见用法

时间:2023-10-24 22:06:37浏览次数:36  
标签:重入 java lock ReentrantLock 用法 util 获取 线程

在进行Java多线程编程的过程中,始终绕不开一个问题:线程安全。一般来说,我们可以通过对一些资源加锁来实现,大多都是通过 synchronized 关键字实现。

在做性能测试时,如果TPS或者QPS要求没有特别高, synchronized 一招鲜基本也能满足大部分的需求了。

对于一招鲜无法很好解决的问题,就需要我们继续探索 java.util.concurrent 包的其他内容。今天就分享一下 java.util.concurrent.locks.Lock 接口的实现类 java.util.concurrent.locks.ReentrantLock 的基本使用方法。

类功能概览

java.util.concurrent.locks.Lock 接口支持三种方法的锁获取:阻塞锁、可中断锁和超时锁。

下面来分享这几种锁的常用的使用场景和案例。

阻塞锁

方法是:java.util.concurrent.locks.ReentrantLock#lock,没有参数。该方法会尝试获取锁。当无法获取锁时,当前线程会处于休眠状态,直到获取锁成功。

演示Demo如下:

private static final Logger log = LogManager.getLogger(LockTest.class);  
  
public static void main(String[] args) throws InterruptedException {  
    ReentrantLock lock = new ReentrantLock();  
    Thread lockTestThread = new Thread(() -> {  
        lock.lock();  
        log.info("获取到锁了!");  
        lock.unlock();  
    });  
    lock.lock();  
    lockTestThread.start(); 
    log.info("即将马上释放锁!"); 
    Thread.sleep(1000);  
    lock.unlock();  
    lockTestThread.join();  
}

控制台打印:

19:43:29 046 main 即将马上释放锁!
19:43:30 050 Thread-2 获取到锁了!
19:43:30 uptime:1 s

由于异步线程获取锁的方法晚于 main 线程,所以会在获取锁的地方阻塞,直至 main 线程将锁释放。可以看到,两条打印日志相差约1s。

可中断锁

可中断锁API是:java.util.concurrent.locks.ReentrantLock#lockInterruptibly。该方式会尝试获取锁,并且是阻塞的,但当未获取到锁时,如果当前线程被设置了中断状态,则会抛出 java.lang.InterruptedException 异常。

演示Demo如下:


private static final Logger log = LogManager.getLogger(LockTest.class);  
  
public static void main(String[] args) throws InterruptedException {  
    ReentrantLock lock = new ReentrantLock();  
    Thread lockTestThread = new Thread(() -> {  
        try {  
            lock.lockInterruptibly();  
            log.info("获取到锁了!");  
            lock.unlock();  
        } catch (InterruptedException e) {  
            log.warn("获取锁失败!", e);  
        }  
  
    });  
    lock.lock();  
    lockTestThread.start();  
    lockTestThread.interrupt();  
    lock.unlock();  
    lockTestThread.join();  
}

控制台打印:

19:58:21 250 Thread-2 获取锁失败!
java.lang.InterruptedException: null
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220) ~[?:1.8.0_281]
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) ~[?:1.8.0_281]
	at com.funtest.temp.LockTest.lambda$main$0(LockTest.java:18) ~[classes/:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_281]

超时锁

超时锁的API有两个:java.util.concurrent.locks.ReentrantLock#tryLock()java.util.concurrent.locks.ReentrantLock#tryLock(long, java.util.concurrent.TimeUnit),返回1个Boolean值,表示获取锁是否成功。第二个API参数设置超时时间。这两个API前者可以简单理解为后者时间设置为0,获取一下试试,成不成都返回结果。

演示Demo如下:

private static final Logger log = LogManager.getLogger(LockTest.class);  
  
public static void main(String[] args) throws InterruptedException {  
    ReentrantLock lock = new ReentrantLock();  
    Thread lockTestThread = new Thread(() -> {  
        boolean b = lock.tryLock();  
        log.info("第一次获取锁的结果:{}", b);  
        try {  
            boolean b1 = lock.tryLock(3, TimeUnit.SECONDS);  
            log.info("第二次获取锁的结果:{}", b1);  
        } catch (InterruptedException e) {  
            log.warn("第二次获取锁的时候被中断了");  
        }  
    });  
    lock.lock();  
    lockTestThread.start();  
    Thread.sleep(1000);  
    lock.unlock();  
    lockTestThread.join();  
}

控制台打印:

20:05:13 559 Thread-2 第一次获取锁的结果:false
20:05:14 563 Thread-2 第二次获取锁的结果:true
20:05:14 uptime:2 s

可以看到再等待了 1s 之后,第二次获取锁成功了。为了简化代码,我并没有写判断获取锁状态的代码。

最佳实践

对于 java.util.concurrent.locks.ReentrantLock ,常用最佳实践只有一个,非常容易掌握。那就是使用 try-catch-finally 语法实现,演示Demo如下:

boolean status = false;  
try {  
    status = lock.tryLock(3, TimeUnit.SECONDS);  
} catch (Exception e) {  
    // 异常处理  
} finally {  
    if (status) lock.unlock();  
}
  1. 尽量使用超时锁
  2. 尽可能少占用锁
  3. 尽量低频使用

可重入

java.util.concurrent.locks.ReentrantLock 直译就是可重入锁,意思是当一个线程获取到锁之后,还可以再获取一次,当然释放也需要两次。在内部有专门用来计数的功能,当然也是线程安全的。

在性能测试实践中,很少能遇到使用 可重入 的特性的场景。所以这里建议不要过度使用 java.util.concurrent.locks.ReentrantLock,复杂场景可以有更加简单可靠的解决方案。

公平锁与非公平锁

java.util.concurrent.locks.ReentrantLock 有一个构造方法,如下:

/**  
 * 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();  
}

方法参数中Boolean值,含义既是是否使用公平锁。无参的构造方法默认使用的非公平锁。公平锁和非公平锁的主要区别是获取锁的方式不同。公平锁的获取是公平的,线程依次排队获取锁。谁等待的时间最长,就由谁获得锁。非公平锁获取是随机的,谁先请求谁先获得锁,不一定按照请求锁的顺序来。

具体区别如下:

  1. 获取锁的方式不同
  • 公平锁:线程依次排队获取锁,效率较低
  • 非公平锁:随机获取锁,效率较高
  1. 性能不同
  • 公平锁:一次性唤醒队列中等待时间最久的线程,Context Switching次数高,性能较低
  • 非公平锁:随机唤醒线程,Context Switching次数低,性能较高
  1. 锁等待时间
  • 公平锁:等待时间长,但访问顺序按队列顺序
  • 非公平锁:等待时间短,但访问顺序随机
  1. 影响因素
  • 公平锁:只影响当前等待的线程,不影响新来线程
  • 非公平锁:可能会无限次让新来线程抢占锁,导致老线程永远获取不到锁
  1. 线程饥饿
  • 公平锁:旧线程有获取锁的机会,相对更公平
  • 非公平锁:可能导致线程饥饿问题

所以综上,非公平锁性能更高,但公平锁更公平。由于性能测试中通常对性能是有要求的,若非强需求,建议尽量使用非公平锁。

标签:重入,java,lock,ReentrantLock,用法,util,获取,线程
From: https://blog.51cto.com/FunTester/8010086

相关文章

  • Wireshark抓包工具的用法
    Wireshark是一款强大的网络分析工具,可以帮助用户深入了解网络数据包的传输过程和内容,从而进行故障排除、网络安全分析和网络性能优化等工作。本文将介绍Wireshark的基本用法和常用功能。一、安装WiresharkWireshark是一款开源软件,可在官方网站上免费下载安装。根据操作系统选择......
  • xpath的contains用法
    xpath('//div[contains(@class,"a")andcontains(@class,"b")]')#它会取class含有有a和b的元素xpath('//div[contains(@class,"a")orcontains(@class,"b")]')#它会取class含有a或者b满足时,或者同时满足时的元素starts-with顾名思义,匹......
  • ACL基本用法
    创建一个文件,使用getfacl命令查看ACL权限,此时显示的内容与先前使用ll命令查看到的内容相差无几。setfacl命令可以设置ACL权限,对每一个文件或目录进行更精确的权限设置,添加“-m”参数可以修改当前文件ACL权限。修改用户下的text.txt文件的读、写权限,当用ll查看文件时,权限字符位最......
  • Awk、Sed、Grep、Cut命令基本用法
    awk、sed、grep、cut是Linux中文本处理、shell开发、数据截取的利器。Sedsed(StreamEditor)是一种流式文本编辑器,常用于对文本文件进行处理和转换。下面是一些常见的sed经典用法:替换文本:sed's/old/new/g'file.txt将文件中所有的"old"替换为"new"。2.删除行:sed'/pattern/d'fil......
  • 17-ReentrantLock和java中的aqs
    一、aqsAQS是AbstractQueuedSynchronizer的缩写,是一个用来构建锁和同步器的框架,是线程安全问题(原子性)的一种解决方案通过它可以实现很多不同类型的锁,例如ReentrantLock。主要内容:用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控......
  • std::istringstream的用法
    1.概要std::istringstream是C++标准库中的一个类,它用于从字符串中提取数据,并将数据转换为不同的数据类型。它通常用于从字符串中解析数据,例如整数、浮点数等。以下是关于std::istringstream的详细用法:创建std::istringstream对象:首先,你需要创建一个std::istringstrea......
  • OS模块的用法
    OS模块的用法os.getcwd()获取当前工作目录,即当前python脚本工作的目录路径os.chdir("dirname")改变当前脚本工作目录;相当于shell下cdos.curdir返回当前目录:('.')os.pardir获取当前目录的父目录字符串名:('..')os.makedirs('dirname1/dirname2')可生成多层递归目......
  • Golang logrus用法
    packagexlogimport( "bufio" "fmt" "github.com/sirupsen/logrus"rotatelogs"github.com/lestrrat-go/file-rotatelogs""github.com/rifflock/lfshook" "os" "time")typeConf......
  • mysqldump之where用法
    文档课题:mysqldump之where用法.数据库:MySQL5.7.21应用场景:实际生产中,需要运用mysqldump导出指定条件的数据,并且以insert语句的形式呈现,如下为相关测试.1、数据库信息mysql>select*fromstaffs;+----+------+-----+---------+---------------------+|id|name|age|p......
  • Object.defineProperty用法
    1、能干啥?Object.defineProperty()可以给传入的对象动态的添加或修改属性2、怎么玩?Object.defineProperty(obj,prop,desc)它有三个参数:obj:需要定义属性的当前对象prop:当前需要定义的属性名;注意是string类型desc:属性描述符;注意是object类型desc常用的属性:value:......