JUC并发编程---Lock锁

news2024/12/26 20:48:54

文章目录

  • 什么是Lock
  • synchronized加锁和Lock加锁代码示例
    • synchronized
    • 使用Lock加锁
  • 公平锁和非公平锁
    • 公平锁:
    • 非公平锁:
    • Lock和Synchronized的区别
  • synchronized 版的生产者和消费者
  • Lock 版的生产者和消费者
  • 生产者和消费者出现的问题
  • Condition精准通知和唤醒线程

什么是Lock

官网介绍:
虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您
需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然
后获取节点B,然后释放A并获取C,然后释放B并获得D等。所述的实施方式中L0Ck接口通过允许获得并在不同的范围释放的锁,并
允许获得并以任何顺序释放多个锁使得能够使用这样的技术。
随着这种增加的灵活性,额外的责任。没有块结构化锁定会删除使用synchronized)方法和语句发生的锁的自动释放。在大多数情
况下,应使用以下惯用语:
Lock l =…1.lock();try /access the resource protected by this lock finally l.unlock();
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁
定。
Lock实现提供了使用synch ronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()),尝试获取可被中断的
锁(lockInterruptibly()),以及尝试获取可以超时(tryLock(Long,TimeUnit))。
一个Lock类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。如果一个实
现提供了这样的专门的语义,那么实现必须记录这些语义。
请注意,Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。获取Lock实例的监视器锁与调用该实例的任何
Lock(防法没有特定关系。建议为避免混淆,您不要以这种方式使用L0ck实例,除了在自己的实现中。
除非另有说明,传递任何参数的nuLl值将导致NullPointerException被抛出。

Lock是一个接口,有三个实现类:ReentrantLock(可重入锁)、ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.writeLock(写锁)

synchronized加锁和Lock加锁代码示例

这里统一用一个买票的例子,多个线程实现不同的窗口进行买票

synchronized

public class TicketSale {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源丢入线程
        Ticket1 ticket = new Ticket1();
        //@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类OOP
class  Ticket1{
    //属性  方法
    private int number = 30;
    //卖票的方式
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}


使用Lock加锁

public class SaleTicketDemo1 {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
    }

}
//资源类
class Ticket{
    //属性
    private int number=50;
    Lock lock=new ReentrantLock();

    public void sale(){
        lock.lock();//加锁
        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();//解锁
        }

    }
}


公平锁和非公平锁

公平锁和非公平锁是在多线程环境下用于同步访问共享资源的机制。它们之间的区别在于线程获取锁的顺序不同。

公平锁:

公平锁是指多个线程按照申请锁的顺序来获取锁,即先来先得的原则。当一个线程释放锁后,等待时间最长的线程会获得锁。
公平锁的优点是保证了资源的公平性,避免了饥饿现象,所有线程都有机会获取到资源。
公平锁的缺点是需要维护一个有序的等待队列,增加了系统开销,降低了并发性能。

非公平锁:

非公平锁是指多个线程获取锁的顺序是不确定的,有可能新申请锁的线程会在等待队列中插队,先于等待时间更长的线程获取到锁。
非公平锁的优点是相对于公平锁,减少了等待时间,提高了吞吐量。
非公平锁的缺点是可能会导致某些线程长时间等待,产生饥饿现象,不公平性可能会造成一些线程无法获得资源。
选择使用公平锁还是非公平锁,取决于具体的业务场景和需求:

如果对资源的访问顺序要求比较高,希望保证公平性,可以选择公平锁。
如果对吞吐量要求比较高,对于资源访问的顺序没有特别的要求,可以选择非公平锁。
需要注意的是,Java中的ReentrantLock默认是非公平锁,但可以通过构造函数参数来指定为公平锁。而synchronized关键字是一种非公平锁。在使用锁的时候,需要根据具体情况选择适合的锁机制。

在ReentrantLock中,默认是非公平锁,如果需要使用公平锁可以通过传入boolean类型的参数进行转换,true为公平锁
在这里插入图片描述

Lock和Synchronized的区别

1、Synchronized内置的ava关键字,Lock是一个ava类
2、Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3、Synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
4、Synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去(tryLock方法);
5、Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!

synchronized 版的生产者和消费者

生产者和消费者之间通过线程通信问题,生产者和消费者问题 等待唤醒 ,通知唤醒

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data{//数字  资源类
    private  int number = 0;
    //加操作
    public  synchronized void increment() throws InterruptedException {
        if(number != 0) {
            //等待
            this.wait();//在哪里睡就在哪里醒
        }
        number++;
        //通知其他线程,我+1完毕了
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
    //减操作
    public synchronized void decrement() throws InterruptedException {
        if(number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
}

上面这段代码是4个线程进行交替。4个线程出现的问题在下文会有讲解。正常可以使用2个线程线程交替

Lock 版的生产者和消费者

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data2 {//数字  资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //    condition.await();//等待
//    condition.signalAll();//唤醒全部
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            if(number != 0) {
                //等待
                condition.await();
            }
            number++;
            //通知其他线程,我+1完毕了
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            if(number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

生产者和消费者出现的问题

在通过上面的代码中,如果两个线程进行的话,可以实现生产者和消费者进行0、1的交替通信,那么如果再增加两个线程的话,就有可能出现问题,并不是0、1进行交替,而是可能出现8、9这样的数值。如图:
在这里插入图片描述

那这种情况就是虚假唤醒,也就是在不该醒的地方,线程醒了并执行了后续的操作。
虚假唤醒出现的原因:
在这里插入图片描述
如上图:当A线程进入阻塞的时候,其他三个线程就会竞争资源,这个使用可能c会获取资源,那么当它执行加操作的时候,会发现不符合条件就会进行阻塞,那么A、B、D进行争抢资源,当到A之后,发现不符合还是会进入阻塞,重复上面的操作,那么可能C抢到资源,这里注意,在刚刚的步骤中,C已经进行了一次阻塞,这里使用的wait(),那么wait是在哪里睡就在哪里醒,这里c就醒了,并执行了后续的加操作。所以才会出现加到7、8这种数值的情况
那么如果解决呢?可以把if判断更换为while判断。

Condition精准通知和唤醒线程

Condition因素出Object监视器方法(wait notify和notifyAll)成不同的对象,以得到具有多个等待集的每个对象,通过
将它们与使用任意的组合的效果Lock个实现。'Lock替换synchronized方法和语句的使用
Condition取代了对象监视器方法的使用。
条件(也称为条件队/域条件变量)为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。
因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。等待条件的关键属性是它原

在这里插入代码片

public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, “A”).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, “B”).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, “C”).start();
}
}
class Data3 {// 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();//监视器
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;//number为1的时候A执行,2的时候B执行,3的时候C执行
public void printA() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + “—>A”);
//唤醒指定的人:B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + “—>B”);
//唤醒指定的人:C
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务,判断->执行->通知
while (number!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+“—>C”);
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
上面代码是通过设置线程执行完之后,后面需要执行那个线程的

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

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

相关文章

机器视觉工程师,人学习最大的能力是理解与善于运用,而不是记住能力

谁记得以前记住的元素周期表&#xff0c;谁能记得住乘法口诀。 如果我们去看一眼&#xff0c;就会迅速记起来。再加上我们小学机械般的练习题。再到我们在现实生活中经常用到。 其实我们机器视觉工程师&#xff0c;一定要去看&#xff0c;还要去练习​。实操软件&#xff0c;多…

深度学习-4-二维目标检测-YOLOv5源码测试与训练

本文采用的YOLOv5源码是ultralytics发行版3.1 YOLOv5源码测试与训练 1.Anaconda环境配置 1.1安装Anaconda Anaconda 是一个用于科学计算的 Python 发行版&#xff0c;支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包。 官方网址下载安装包&…

【SQL应知应会】索引 • Oracle版:B-树索引;位图索引;函数索引;单列与复合索引;分区索引

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文免费学习&#xff0c;自发文起3天后&#xff0c;会收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle …

面试被打脸,数据结构底层都不知道么--回去等通知吧

数据结构之常见的8种数据结构&#xff1a; -数组Array -链表 Linked List -堆 heap -栈 stack -队列 Queue -树 Tree -散列表 Hash -图 Graph 数据结构-链表篇 Linklist定义&#xff1a; -是一种线性表&#xff0c;并不会按线性的顺序存储数据&#xff0c;即逻辑上相邻…

解码自我注意的魔力:深入了解其直觉和机制

一、说明 自我注意机制是现代机器学习模型中的关键组成部分&#xff0c;尤其是在处理顺序数据时。这篇博文旨在提供这种机制的详细概述&#xff0c;解释它是如何工作的&#xff0c;它的优点&#xff0c;以及它背后的数学原理。我们还将讨论它在变压器模型中的实现和多头注意力的…

设计模式-10--多例模式(Multition pattern)

一、什么是多例模式&#xff08;Multition pattern&#xff09; 多例模式&#xff08;Multition pattern&#xff09;是单例模式的一种扩展&#xff0c;它属于对象创建类型的设计模式。在多例模式中&#xff0c;一个类可以有多个实例&#xff0c;并且这些实例都是该类本身。因…

实现不同局域网间的文件共享和端口映射,使用Python自带的HTTP服务

文章目录 1. 前言2. 本地文件服务器搭建2.1 python的安装和设置2.2 cpolar的安装和注册 3. 本地文件服务器的发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 数据共享作为和连接作为互联网的基础应用&#xff0c;不仅在商业和办公场景有广泛的应用…

设计模式-5--适配器模式(Adapter Pattern)

一、什么是适配器模式&#xff08;Adapter Pattern&#xff09; 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式主要用于解决不兼容接口之间的问题&#xff0c;使得原本…

Windows安装jdk

Windows安装jdk 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.准备安装包&#xff08;需要的滴滴我&#xff09; 2.安装 我是在d盘创建jdk目录&#xff0c;把jdk包解压到jdk里 计算机右键---属性---高级系统设置—环境变量 &#xff08;系统变量里&#xff09;--新…

小兔鲜儿 - 地址模块

目录 小兔鲜儿 - 地址模块 准备工作​ 静态结构​ 地址管理页​ 地址表单页​ 动态设置标题​ 新建地址页​ 接口封装​ 参考代码​ 地址管理页​ 接口调用​ 参考代码​ 修改地址页​ 数据回显​ 更新地址​ 表单校验​ 操作步骤​ 删除地址​ 侧滑组件用法…

Leetcode 剑指 Offer II 042. 最近的请求次数

题目难度: 简单 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请实…

4.2 实现基于栈的表达式求值计算器(难度4/10)

本作业主要考察&#xff1a;解释器模式的实现思想/栈结构在表达式求值方面的绝对优势 C数据结构与算法夯实基础作业列表 通过栈的应用&#xff0c;理解特定领域设计的关键作用&#xff0c;给大家眼前一亮的感觉。深刻理解计算机语言和人类语言完美结合的杰作。是作业中的上等…

钉钉机器人消息推送composer拓展 laravel-dingbot

钉钉机器人消息发送 介绍 企业内部有较多系统支撑着公司的核心业务流程&#xff0c;譬如CRM系统、交易系统、监控报警系统等等。通过钉钉的自定义机器人&#xff0c;可以将这些系统事件同步到钉钉的聊天群。 laravel-dingbot 是一款钉钉机器人消息推送的Laravel扩展&#xff…

vscode c语言代码自动格式化

1、在vscode扩展商店里面搜索Clang-format&#xff0c;安装第1个插件 2、快捷键Ctrl逗号&#xff0c;输入format&#xff0c;选择Clang-Format configuration进行配置&#xff08;其实默认就可以&#xff09; 3、vscode打开文件夹的源码&#xff0c;在该文件夹里面新建一个文件…

[前端必看,后端福利❤]如何创建美观的邮件模板并通过qq邮箱的SMTP服务向用户发送

最近在写注册功能的自动发送邮箱告知验证码的功能&#xff0c;无奈根本没有学过前端&#xff0c;只有写Qt的qss基础&#xff0c;只好借助网页设计自己想要的邮箱格式&#xff0c;最终效果如下: 也推销一下自己的项目ShaderLab&#xff0c;可运行ShaderToy上的大部分着色器代码&…

js只保留数组对象的某个属性,合并公共类型的数据,选择树形结构的数据,并保留每个节点的name

嗨&#xff0c;今天周二了哎&#xff01; 期待周五 文章目录 一、js只保留数组对象的某个属性二、合并公共类型的数据二、选择树形结构的数据&#xff0c;并保留每个节点的name 一、js只保留数组对象的某个属性 let data [{ id: 1, name: 哈哈 }, { id: 2, name: 嘻嘻 }]let n…

ModaHub魔搭社区——大模型能力落地和核心就是应用场景

从今年3月百度率先发布语言大模型生成式AI产品“文心一言”后,各大科技互联网巨头纷纷入局,国内大模型瞬间遍地开花。包括阿里、华为、商汤科技、科大讯飞、360、腾讯等,纷纷推出各类大模型。 人工智能正在进入大规模落地应用关键期。 在IDC近日发布的《中国人工智能公有云…

本地虚机Jumpserver使用域名访问报错 使用IP+端口没有错误

背景&#xff1a; 我在本地Windows VMware 15的环境中部署了CentOS7.5&#xff0c;下载jumpserver-offline-installer-v2.28.1-amd64-138.tar.gz并安装部署。 需求&#xff1a; 1、能使用http:ip访问堡垒机。达成&#xff1b; 2、能使用http:域名访问堡垒机。达成&#xff…

FPGA时序分析与约束(2)——时序电路时序

一、前言 在之前的内容中&#xff0c;我们介绍了组合电路的时序问题和可能导致的毛刺&#xff0c;强烈推荐在阅读前文的基础上再继续阅读本文&#xff0c; 前文链接&#xff1a;FPGA时序分析与约束&#xff08;1&#xff09;——组合电路时序 这篇文章中&#xff0c;我们将继续…

Android安卓webview,网页端生成安卓项目(极速生成)教程

Android安卓webview&#xff0c;网页端生成安卓项目&#xff08;极速生成&#xff09;教程 一&#xff0c;前言 当自己做了一个PC端的页面&#xff0c;也就是前端的页面&#xff0c;或者已经上服的页面&#xff0c;但也想生成一个安卓端供用户使用&#xff0c;本教程详细讲解…