《Java并发编程实战》课程笔记(四)

news2024/11/20 20:21:14

互斥锁

原子性问题到底该如何解决呢?

  • “同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。
  • 如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。

锁模型

在这里插入图片描述

  • 首先,我们要把临界区要保护的资源标注出来,如图中临界区里增加了一个元素:受保护的资源 R;
  • 其次,我们要保护资源 R 就得为它创建一把锁 LR;
  • 最后,针对这把锁 LR,我们还需在进出临界区时添上加锁操作和解锁操作。

Java 语言提供的锁技术:synchronized

  • 锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现。
  • synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。
    class X {
        // 修饰非静态方法
        synchronized void foo() {
            // 临界区
        }
        // 修饰静态方法
        synchronized static void bar() {
            // 临界区
        }
        // 修饰代码块
        Object obj = new Object();
        void baz() {
            synchronized (obj) {
                // 临界区
            }
        }
    }
    
    • Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock()。
    • 这样做的好处就是加锁 lock() 和解锁 unlock() 一定是成对出现的,毕竟忘记解锁 unlock() 可是个致命的 Bug(意味着其他线程只能死等下去了)。
    • 当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
    • 当修饰非静态方法的时候,锁定的是当前实例对象 this

锁和受保护资源的关系

  • 受保护资源和锁之间的关联关系是 N:1 的关系。

保护没有关联关系的多个资源

  • 例如,银行业务中有针对账户余额(余额是一种资源)的取款操作,也有针对账户密码(密码也是一种资源)的更改操作,我们可以为账户余额和账户密码分配不同的锁来解决并发问题,这个还是很简单的。
    class Account {
        // 锁:保护账户余额
        private final Object balLock = new Object();
        // 账户余额
        private Integer balance;
        // 锁:保护账户密码
        private final Object pwLock = new Object();
        // 账户密码
        private String password;
    
        // 取款
        void withdraw(Integer amt) {
            synchronized(balLock) {
                if (this.balance > amt){
                    this.balance -= amt;
                }
            }
        }
        // 查看余额
        Integer getBalance() {
            synchronized(balLock) {
                return balance;
            }
        }
    
        // 更改密码
        void updatePassword(String pw){
            synchronized(pwLock) {
                this.password = pw;
            }
        }
        // 查看密码
        String getPassword() {
            synchronized(pwLock) {
                return password;
            }
        }
    }
    
    • 账户类 Account 有两个成员变量,分别是账户余额 balance 和账户密码 password。
    • 取款 withdraw() 和查看余额 getBalance() 操作会访问账户余额 balance,我们创建一个 final 对象 balLock 作为锁(类比球赛门票);
    • 更改密码 updatePassword() 和查看密码 getPassword() 操作会修改账户密码 password,我们创建一个 final 对象 pwLock 作为锁(类比电影票)。
    • 不同的资源用不同的锁保护,各自管各自的,很简单。
    • 用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁。

保护有关联关系的多个资源

  • 例如银行业务里面的转账操作,账户 A 减少 100 元,账户 B 增加 100 元。这两个账户就是有关联关系的。
    class Account {
        private int balance;
        // 转账
        void transfer(
                Account target, int amt){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
    
    • 我们声明了个账户类:Account,该类有一个成员变量余额:balance,还有一个转账的方法:transfer()。
    • 用同一把锁来保护多个资源,也就是现实世界的“包场”,那在编程领域应该怎么“包场”呢?很简单,只要我们的锁能覆盖所有受保护资源就可以了。
  • 用 Account.class 作为共享的锁。
    • Account.class 是所有 Account 对象共享的,而且这个对象是 Java 虚拟机在加载 Account 类的时候创建的,所以我们不用担心它的唯一性。
      class Account {
          private int balance;
          // 转账
          void transfer(Account target, int amt){
              synchronized(Account.class) {
                  if (this.balance > amt) {
                      this.balance -= amt;
                      target.balance += amt;
                  }
              }
          }
      }
      

在这里插入图片描述

  • 相对于用 Account.class 作为互斥锁,锁定的范围太大,而我们锁定两个账户范围就小多了:
    class Account {
        private int balance;
        // 转账
        void transfer(Account target, int amt){
            // 锁定转出账户
            synchronized(this) {
                // 锁定转⼊账户
                synchronized(target) {
                    if (this.balance > amt) {
                        this.balance -= amt;
                        target.balance += amt;
                    }
                }
            }
        }
    }
    

在这里插入图片描述

  • 使用细粒度锁可以提高并行度,是性能优化的一个重要手段。使用细粒度锁是有代价的,这个代价就是可能会导致死锁。

死锁

  • 死锁的一个比较专业的定义是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

如何预防死锁

  • 并发程序一旦死锁,一般没有特别好的方法,很多时候我们只能重启应用。因此,解决死锁问题最好的办法还是规避死锁。
  • 死锁的四个必要条件:
    • 互斥,共享资源 X 和 Y 只能被一个线程占用;
    • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
    • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
    • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
  • 也就是说只要我们破坏其中一个,就可以成功避免死锁的发生。
    • 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。
    • 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。
    • 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。

用“等待-通知”机制优化循环等待

  • 一个完整的等待-通知机制:线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

用 synchronized 实现等待-通知机制

  • 在 Java 语言里,等待-通知机制可以有多种实现方式,比如 Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法就能轻松实现。
    在这里插入图片描述
    • 左边有一个等待队列,同一时刻,只允许一个线程进入synchronized 保护的临界区,当有一个线程进入临界区后,其他线程就只能进入图中左边的等待队列里等待。
    • 这个等待队列和互斥锁是一对一的关系,每个互斥锁都有独立的等待队列。
    • 线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获得锁,并进入临界区了。
  • 那线程要求的条件满足时,该怎么通知这个等待的线程呢?很简单,就是 Java 对象的 notify() 和 notifyAll() 方法。
    • 当条件满足时调用 notify(),会通知等待队列(互斥锁的等待队列)中的线程,告诉它条件曾经满足过。
    • 为什么说是曾经满足过呢?
      • 因为 notify() 只能保证在通知时间点,条件是满足的。而被通知线程的执行时间点和通知的时间点基本上不会重合,所以当线程执行的时候,很可能条件已经不满足了(保不齐有其他线程插队)。
      • 除此之外,还有一个需要注意的点,被通知的线程要想重新执行,仍然需要获取到互斥锁(因为曾经获取的锁在调用 wait() 时已经释放了)。

尽量使用 notifyAll()

  • notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程。
  • 实际上使用 notify() 也很有风险,它的风险在于可能导致某些线程永远不会被通知到。所以除非经过深思熟虑,否则尽量使用 notifyAll()。

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

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

相关文章

Python连接达梦数据库

python如果想连接达梦数据库,必须要安装dmPython。 简介:dmPython 是 DM 提供的依据 Python DB API version 2.0 中 API 使用规定而开发的数据库访问接口。dmPython 实现这些 API,使 Python 应用程序能够对 DM 数据库进行访问。 dmPython 通…

数据库服务器

数据库服务器,联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器,包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…

系统漏洞利用与提权

任务二:系统漏洞利用与提权 任务环境说明: 服务器场景:PYsystem0033 服务器场景操作系统:Ubuntu 服务器场景用户名:未知 密码:未知 1.使用nmap扫描靶机系统,将靶机开放的端口号按从小到大的顺序作为F…

解决Vmware上的kali找不到virtualbox上的靶机的问题

解决kali找不到靶场ip问题的完整方法 1.配置靶机2.配置kali的虚拟网络3.配置kali中的eth0网络 1.配置靶机 靶机部署在Virtualbox上对其进行网络配置,选择连接方式为仅主机(Host-Only)网络。 2.配置kali的虚拟网络 在编辑中选择虚拟网络配…

chatgpt赋能python:Python中浮点数的表示方法

Python中浮点数的表示方法 在Python中,浮点数是一种数字类型,用于表示带有小数点的数值。但是,由于计算机在表示浮点数时存在精度限制,因此需要特别注意。本文将介绍Python中浮点数的表示方法及其可能导致的错误。 Python中浮点…

陕西发布!陕西省重点实验室申报条件类别、认定程序要求

本文整理了陕西省重点实验室申报条件,认定材料等相关内容,感兴趣的朋友快跟小编一起来看看吧! 一、总体思路 本次省重点实验室布局建设工作以填补我省优势学科领域下无省级及以上科学与工程研究类科技创新基地的空白为主,同时兼顾前沿、新兴、…

MySQL基础- 多表查询 和 事务

目录 多表查询多表关系多表查询概述多表查询的分类内连接外连接自连接联合查询union,union all子查询标量子查询列子查询行子查询表子查询 综合练习小结 事务事务简介事务的操作四大特性ACID并发事务问题事务的隔离级别小结 多表查询 之前的SQL语句里的DQL只能进行…

数字图像学笔记 —— 18. 图像抖动算法

文章目录 为什么需要图像抖动图像抖动算法实现的基本思路常见图像抖动算法实现Floyd-Steinberg 抖动算法Atkinson 抖动算法算法实现 为什么需要图像抖动 在数字图像中,为了表示数字图像的细节,像素的颜色深度信息最少也是8位,即 0 − 256 0…

Linux:centos:周期性计划任务管理《crontab》

crontab常用基础属性 -e 编辑计划任务 -l 查看计划任务 -r 删除计划任务 -u 指定用户的计划任务 首先创建一个名为test的用户名 crontab时间规定 格式:分钟 小时 日期 月份 星期 命令 分钟-- 0-59整数 小时 -- 0-23整数 日期 -- 1--31 整数 月份 -- 1-12 整数 星期…

C++ queue类成员介绍

目录 🤔queue模板介绍: 🤔queue特点: 🤔queue内存图解: 🤔 queue的成员函数 🔍queue构造函数: 🔍queue赋值函数: 🔍queue判断函…

黑马Redis视频教程实战篇(三)

目录 一、优惠券秒杀 1.1 全局唯一ID 1.2 Redis实现全局唯一ID 1.3 添加优惠卷 1.4 实现秒杀下单 1.5 库存超卖问题分析 1.6 代码实现乐观锁解决超卖问题 1.7 优惠券秒杀-一人一单 1.8 集群环境下的并发问题 二、分布式锁 2.1 基本原理和实现方式对比 2.2 Redis分布…

js常见面试笔试题

一.js实现距离最近的回文数 给定一个整数 n ,你需要找到与它最近的回文数(不包括自身)。 “最近的”定义为两个整数差的绝对值最小。 示例 1: 输入: "123" 输出: "121" function findNearestPalindrome…

Jenkins+Python自动化测试之持续集成详细教程

前言 今天呢笔者想和大家来聊聊JenkinsPython自动化测试持续集成,废话呢就不多说了哟咱们直接进入主题哟。 一、Jenkins安装 ​ Jenkins是一个开源的软件项目,是基于java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供…

I.MX RT1170加密启动详解(2):Authenticated HAB认证原理

文章目录 1 基础2 使能过程3 Boot flow 1 基础 HAB认证是基于RSA或ECDSA算法的公钥密码学,它用一系列的私钥对image进行加密,然后BootROM在上电后用对应的公钥验证加密的镜像是否被修改。这个密钥结构就是PKI(Public Key Infrastructure)树 (1)normal …

chatgpt赋能python:Python中画笔颜色的函数介绍

Python中画笔颜色的函数介绍 在Python中,我们可以使用turtle模块来绘制图形,其中画笔颜色是非常重要的一部分。画笔颜色可以决定图形的风格和色调,是图形表现的关键因素之一。Python中提供了几种方法来设置画笔颜色。 1. 设置画笔颜色的函数…

ENU、EPSG坐标系科普(三维重建)

ENU和EPSG实际上代表了两个不同的概念,这两者并不是直接对比的。 1. ENU坐标系:ENU坐标系是一种本地切面坐标系,用于表示与地理位置相关的空间数据。在ENU坐标系中,E代表东(East),N代表北&…

RabbittMQ快速实战和集群架构

介绍对比: Kafka:topic不能太多,一个缺点,影响Kafka的吞吐量 集群搭建:【单个也是一个集群(特殊)】 集群搭建:https://blog.csdn.net/p393975269/article/details/129830252 1:默认…

【明解STM32】中断系统理论基础知识篇之中断寄存器功能原理

目录 一、前言 二、寄存器概述 三、NVIC寄存器组 四、SCB寄存器组 五、中断屏蔽寄存器组 六、总结 一、前言 在之前的STM32的中断系统理论基础知识之基本原理及NVIC中,分别中断的基本原理,中断的管理机制和中断的处理流程进行了较为详细的论述&…

SOLIDWORKS钣金成形工具

SOLIDWORKS钣金成形工具主要用来创建使用冲制或压印制作的钣金特征。成形工具的工作原理是:几何体代表冲制或压印形成的凹陷区域,停止面是指工具要被应用到的钣金面,也可以定义移除面,若定义了移除面,则该面会形成通孔…

模拟人生打开显示找不到msvcp120.dll如何修复?msvcp120.dll是什么呢?

如果您在打开模拟人生游戏时遇到了msvcp120.dll文件丢失的错误信息,这可能是由于缺少Microsoft Visual C Redistributable for Visual Studio 2013软件包导致的。遇到这个情况不需要着急,只需要修复一下就可以。msvcp120.dll如何修复?下面就把…