02_Lock锁

news2025/1/13 15:43:56

首先看一下JUC的重磅武器——锁(Lock)

相比同步锁,JUC包中的Lock锁的功能更加强大,它提供了各种各样的锁(公平锁,非公平锁,共享锁,独占锁……),所以使用起来很灵活。

翻译过来就是:

锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。

Lock是一个接口,这里主要有三个实现:

  • ReentrantLock
  • ReentrantReadWriteLock.ReadLock
  • ReentrantReadWriteLock.WriteLock

一、ReentrantLock可重入锁(递归锁) 

使用ReentrantLock改造卖票程序:只需改造sale()方法

private ReentrantLock lock = new ReentrantLock();  //创建实例对象

lock.lock(); //加锁

lock.unlock(); //释放锁

class Ticket{

    private Integer number = 20;

    private ReentrantLock lock = new ReentrantLock();

    public void sale(){

        lock.lock();

        if (number <= 0) {
            System.out.println("票已售罄!");
            lock.unlock();
            return;
        }

        try {
            Thread.sleep(200);
            number--;
            System.out.println(Thread.currentThread().getName() + "买票成功,当前剩余:" + number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

1. 测试可重入性

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁。Java中ReentrantLock和synchronized都是可重入锁可重入锁的一个优点是可一定程度避免死锁。

例如下列伪代码:

class A{
	public synchronized void aa{
		......
        bb();
        ......
	}
	public synchronized void bb{
		......
	}
}
A a = new A();
a.aa();

A类中有两个普通同步方法,都需要对象a的锁。如果是不可重入锁的话,aa方法首先获取到锁,aa方法在执行的过程中需要调用bb方法,此时锁被aa方法占有,bb方法无法获取到锁,这样就会导致bb方法无法执行,aa方法也无法执行,出现了死锁情况。可重入锁可避免这种死锁的发生。

class Ticket{

    private Integer number = 20;

    private ReentrantLock lock = new ReentrantLock();

    public void sale(){

        lock.lock();

        if (number <= 0) {
            System.out.println("票已售罄!");
            lock.unlock();
            return;
        }

        try {
            Thread.sleep(200);
            number--;
            System.out.println(Thread.currentThread().getName() + "买票成功,当前剩余:" + number);
            // 调用check方法测试锁的可重入性
            this.check();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 为了测试可重入锁,添加检查余票方法
     */
    public void check(){
        lock.lock();
        System.out.println("检查余票。。。。");
        lock.unlock();
    }
}

可以发现程序可以正常执行。。。说明该锁确实可重入。

AAA买票成功,当前剩余:19
检查余票。。。。
AAA买票成功,当前剩余:18
检查余票。。。。
AAA买票成功,当前剩余:17
检查余票。。。。
AAA买票成功,当前剩余:16
检查余票。。。。
AAA买票成功,当前剩余:15
检查余票。。。。
AAA买票成功,当前剩余:14
检查余票。。。。
AAA买票成功,当前剩余:13
检查余票。。。。
BBB买票成功,当前剩余:12
检查余票。。。。
BBB买票成功,当前剩余:11
检查余票。。。。
BBB买票成功,当前剩余:10
。。。。。。

2. 测试公平锁

ReentrantLock还可以实现公平锁。所谓公平锁,也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。

ReentrantLock lock = new ReentrantLock();         //默认非公平锁
ReentrantLock lock = new ReentrantLock(true);     //true表示创建公平锁

    //默认非公平锁
    //ReentrantLock lock = new ReentrantLock();
    //true表示创建公平锁
    ReentrantLock lock = new ReentrantLock(true);
    public void test() throws InterruptedException {
        //lock.tryLock():获取锁并立即返货获取锁的结果,成功返回true,失败false
//        lock.tryLock(timeout,timeunit): 最多阻塞等待timeout单位timeunit 时间,获取成功返回true,失败false
        if(lock.tryLock(6, TimeUnit.SECONDS)){//获取锁成功
            System.out.println(Thread.currentThread().getName()+"开始执行...");
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName()+"执行结束...");
            lock.unlock();
        }else{
            System.out.println(Thread.currentThread().getName()+"获取锁失败....");
        }

    }
}

测试结果:可以看到ABC三个线程是按顺序买票成功的。

AAA买票成功,当前剩余:19
检查余票。。。。
BBB买票成功,当前剩余:18
检查余票。。。。
CCC买票成功,当前剩余:17
检查余票。。。。
AAA买票成功,当前剩余:16
检查余票。。。。
BBB买票成功,当前剩余:15
检查余票。。。。
CCC买票成功,当前剩余:14
。。。。。。

3. 限时等待

这个是什么意思呢?也就是通过我们的tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题

lock.tryLock():获取锁并立即返货获取锁的结果,成功返回true,失败false
lock.tryLock(timeout,timeunit): 最多阻塞等待timeout单位timeunit 时间,获取成功返回true,失败false
 

public class Demo3 {
    public static void main(String[] args) {
        Demo3 demo3 = new Demo3();
        new Thread(()->{
            demo3.test();
        },"A").start();
        new Thread(()->{
            demo3.test();
        },"B").start();
    }
    Lock lock = new ReentrantLock();
    public void test(){
        try {
            boolean b = lock.tryLock(1000, TimeUnit.MILLISECONDS);
            if(!b){
                System.out.println(Thread.currentThread().getName()+"获取锁失败");
                return;
            }
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+"....");
            lock.unlock();

        } catch (InterruptedException e) {
            e.printStackTrace();
            lock.unlock();
        }
    }
}

4. ReentrantLock和synchronized区别

(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。

(4)synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。

二、ReentrantReadWriteLock读写锁

在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用java提供的关键字synchronized或者concurrents包中实现了Lock接口的ReentrantLock。它们都是独占式获取锁,也就是在同一时刻只有一个线程能够获取锁

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。大部分只是读数据,写数据很少,如果仅仅是读数据的话并不会影响数据正确性(出现脏读),而如果在这种业务场景下,依然使用独占锁的话,很显然这将是出现性能瓶颈的地方。针对这种读多写少的情况,java还提供了另外一个实现Lock接口的ReentrantReadWriteLock(读写锁)。读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。

接下来以缓存为例用代码演示读写锁,重现问题:

class MyCache{
    
    private volatile Map<String, String> cache= new HashMap<>();

    public void put(String key, String value){
        try {
            System.out.println(Thread.currentThread().getName() + " 开始写入!");
            Thread.sleep(300);
            cache.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写入成功!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
        }
    }

    public void get(String key){
        try {
            System.out.println(Thread.currentThread().getName() + " 开始读出!");
            Thread.sleep(300);
            String value = cache.get(key);
            System.out.println(Thread.currentThread().getName() + " 读出成功!" + value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

public class ReentrantReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache cache = new MyCache();

        for (int i = 1; i <= 5; i++) {
            String num = String.valueOf(i);
            // 开启5个写线程
            new Thread(()->{
                cache.put(num, num);
            }, num).start();
        }
        for (int i = 1; i <= 5; i++) {
            String num = String.valueOf(i);
            // 开启5个读线程
            new Thread(()->{
                cache.get(num);
            }, num).start();
        }
    }
}

打印结果:多执行几次,有很大概率不会出现问题

改造MyCache,加入读写锁

class MyCache{
    private volatile Map<String, String> cache= new HashMap<>();
    // 加入读写锁
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public void put(String key, String value){
        // 加写锁
        rwl.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 开始写入!");
            Thread.sleep(500);
            cache.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写入成功!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放写锁
            rwl.writeLock().unlock();
        }
    }

    public void get(String key){
        // 加入读锁
        rwl.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 开始读出!");
            Thread.sleep(500);
            String value = cache.get(key);
            System.out.println(Thread.currentThread().getName() + " 读出成功!" + value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放读锁
            rwl.readLock().unlock();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/468162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shell终端敲入命令计算机都做了什么?

本文参考&#xff1a; linux命令行的运行原理是什么&#xff1f; - 知乎 (zhihu.com) 8.1 键盘敲入 A 字母时&#xff0c;操作系统期间发生了什么&#xff1f; | 小林coding (xiaolincoding.com) shell命令背后的执行过程_shell 命令执行的产生的进程_kyrieguard的博客-CSDN博客…

三维动画渲染用什么软件好?

三维渲染是通过计算机应用程序把 3D 模型生成图像&#xff08;照片级真实感或非照片级真实感&#xff09;的自动化过程&#xff0c;三维动画渲染是动画制作过程的最后一步&#xff0c;该过程将各种视觉效果应用于最终模型&#xff0c;例如阴影、纹理、灯光反射和运动模糊等&…

史上最全的接口测试,吐血整理从零到接口自动化实战...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口测试&#xf…

04_并发容器类

1. 重现线程不安全&#xff1a;List 首先以List作为演示对象&#xff0c;创建多个线程对List接口的常用实现类ArrayList进行add操作。 public class NotSafeDemo {public static void main(String[] args) {List<String> list new ArrayList<>();for (int i 0; i…

keil移植linux(makefile)

文章目录 运行环境&#xff1a;1.1 freeRTOS_LED工程移植1)修改cubeMX配置2)setting设置3)launch设置4)修改makefile5)修改代码6)实验效果 运行环境&#xff1a; ubuntu18.04.melodic 宏基暗影骑士笔记本 stm32f427IIH6 stlink 9-24v可调电源 robomaster A 板 1.1 freeRTOS_L…

Kubernetes集群的类似top的查看指标的工具ktop

Kubernetes集群的类似top的工具。 遵循Unix/Linux顶级工具的传统&#xff0c;ktop是一个显示有关Kubernetes集群中运行的节点、pod和其他工作负载资源的有用度量信息的工具。 项目地址&#xff1a;https://github.com/vladimirvivien/ktop使用效果图&#xff1a; 特性 集群资…

Linux如何压缩和解压文件

先看压缩 Linux zip 命令用于压缩文件。 zip 是个使用广泛的压缩程序&#xff0c;压缩后的文件后缀名为 .zip 将 /home/html/ 这个目录下所有文件和文件夹打包为当前目录下的 html.zip&#xff1a; zip -r html.zip /home/html 如果在我们在 /home/html 目录下&#xff0c;可…

经济回暖、兴趣电商升级,品牌在竞争白热化的市场中如何突围?| D3大会圆桌回顾

冬去春来&#xff0c;消费市场韧性回弹&#xff0c;消费趋势正处于“转折”和“跃升”的阶段。新的机遇和挑战也将伴随着新的思维、方法和模式&#xff0c;呈现出更多元的变化和创新&#xff1a;渠道虚实融合&#xff0c;内容为王&#xff0c;社会化媒体成为主战场等消费场景不…

13-NumPy

文章目录 一.基础1.Ndarray对象2.数据类型 二.数组1.数组属性&#xff08;1&#xff09;arange&#xff08;2&#xff09;shape&#xff08;3&#xff09;ndim&#xff08;4&#xff09;itemsize 2.创建数组&#xff08;1&#xff09;empty&#xff08;2&#xff09;zero&#…

【接口自动化测试】月薪12k必会技术,从0到1学习接口自动化测试,6个操作安排的明明白白

导读&#xff1a;在所有的开发测试中&#xff0c;接口测试是必不可少的一项。有效且覆盖完整的接口测试&#xff0c;不仅能保障新功能的开发质量&#xff0c;还能让开发在修改功能逻辑的时候有回归的能力&#xff0c;同时也是能优雅地进行重构的前提。编写接口测试要遵守哪些原…

电商直播商家崛起所面临的问题,订单管理系统起关键性作用

随着短视频直播的兴起&#xff0c;电商企业再次迎来大爆发&#xff0c;随着销量的猛增&#xff0c;随之而来的的订单处理成了各大商家的头等大事&#xff0c;面对再次崛起的电商蓝库云认为企业在订单管理会经常遇到以下问题&#xff1a; 电商企业在订单管理中可能面临以下问题…

js版计算连续12个月计算不超3万公里

<!--考虑比亚迪车友不是程序员的多&#xff0c;写了个html版的&#xff0c;复制以下代码在记事本&#xff0c;改后缀名为test.html&#xff0c;然后用浏览器打开--> <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>…

基于SpringBoot3从零配置MybatisPlus

基于SpringBoot3从零配置MybatisPlus记录 文章目录 1.环境2.表数据准备3. 配置pom配置yml 配置MapperScan 3.问题总结问题1: Property sqlSessionFactory or sqlSessionTemplate are required问题2&#xff1a;org.apache.ibatis.binding.BindingException: Invalid bound stat…

Python学习13:说句心里话 A(python123)

描述 分两次从控制台接收用户的两个输入&#xff1a;第一个内容为"人名"&#xff0c;第二个内容为"心里话"。‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪…

使用D435i深度相机运行ORB-SLAM3

下载安装链接 下载ORB-SLAM3地址&#xff1a; git clone https://github.com/UZ-SLAMLab/ORB_SLAM3.git eigen3多版本安装&#xff1a;https://blog.csdn.net/weixin_41756645/article/details/129570141 ORB-SLAM2中eigen3版本为&#xff1a;3.2.10版本 ORB-SLAM3中eigen3版…

【Jetpack】ViewModel + LiveData + DataBinding 综合使用 ( 核心要点说明 | 组合方式 | 代码示例 )

文章目录 一、ViewModel LiveData DataBinding 核心要点1、ViewModel 使用要点2、LiveData 使用要点3、DataBinding 使用要点 二、ViewModel LiveData DataBinding 代码示例1、ViewModel LiveData 代码2、build.gradle 构建脚本 - 启用 DataBinding3、DataBinding 布局文件…

ChatGPT实战100例 - (10) 提前体验ChatGPT的多模态绘图功能

文章目录 ChatGPT实战100例 - (10) 提前体验ChatGPT的多模态绘图功能一、需求与思路二、基本调教三、开始秀四、 总结 ChatGPT实战100例 - (10) 提前体验ChatGPT的多模态绘图功能 这个绘图其实比较基础&#xff0c;只能说是能显示个图吧 真要出图&#xff0c;隔壁 文心一言 秒杀…

如何提高三维模型OSGB格式转换3DTILES的转换速度和数据质量

如何提高三维模型OSGB格式转换3DTILES的转换速度和数据质量 提高三维模型从OSGB格式转换为3DTILES格式的转换速度和数据质量&#xff0c;可以从以下几个方面进行优化&#xff1a; 1、选用高效的转换工具&#xff1a;选择高效的转换工具是提高转换速度和数据质量的关键。目前市…

.NET中比肩System.Text.Json序列化反序列化组件MessagePack

简介 官方定义&#xff1a;MessagePack是一种高效的二进制序列化格式。它允许您像JSON一样在多个语言之间交换数据。但是它更快并且更小。 MessagePack是一种开源的序列化反序列化组件&#xff0c;可支持JAVA&#xff0c;C#等主流语言。在 C# 中使用 MessagePack&#xff0c…

javaScript:cropperjs是一款非常强大却又简单的图片裁剪工具

cropperjs是一款非常强大却又简单的图片裁剪工具&#xff0c;它可以进行非常灵活的配置&#xff0c;支持手机端使用&#xff0c;支持包括IE9以上的现代浏览器。&#xff08;关键是使用方法简单&#xff0c;几行代码就可以搞定&#xff09; 官方github文档&#xff1a;GitHub -…