秋招Java后端开发冲刺——并发篇2(JMM与锁机制)

本文对Java的内存管理模型、volatile关键字和锁机制进行详细阐述,包括synchronized关键字、Lock接口及其实现类ReentrantLock、AQS等的实现原理和常见方法。

一、JMM(Java内存模型)

1. 介绍
JMM定义了共享内存多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
2. 模型
在这里插入图片描述

  • JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
  • 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存

二、Volatile关键字

1. 介绍

  • Volatile关键字主要用于修饰成员变量(实例变量或类变量),使得对该变量的读写操作直接与主内存交互,而不是通过线程本地的工作内存进行缓存。
  • Volatile关键字提供了一种轻量级的同步机制,用于保证多线程环境下数据的可见性(但不能保证数据的原子性,Synchronized关键字既保证可见性,又保证原子性)

2. Volatile禁止指令重排
(1)指令重排

指令重排是一种编译器和处理器优化技术,它改变指令执行的顺序,以提高性能。在单线程环境下,这种优化不会影响程序的正确性。但是在多线程环境下,指令重排可能导致意想不到的结果,破坏程序的正确性。

(2)Volatile如何禁止指令重排

  • 通过在读写 volatile 变量时插入内存屏障(Memory Barriers)来实现
    • 写屏障(Write Barrier):确保在写 volatile 变量之前的所有写操作在内存中完成。
    • 读屏障(Read Barrier):确保在读 volatile 变量之后的所有读操作在内存中完成
  • volatile关键字一般加在写变量最后一个,读变量第一个。
class Example {
    private volatile boolean flag = false;
    private int a = 0;

    public void writer() {
        a = 1;           // 1
        flag = true;     // 2
    }

    public void reader() {
        if (flag) {      // 3
            int i = a;   // 4
        }
    }
}

上述示例中:

  • 在writer方法中,写入a = 1(1)操作不能被重排序到写入flag = true(2)之后。因为flag是volatile,在写flag的时候会插入一个StoreStore屏障,确保在flag被写入之前,所有之前的写操作都完成了。
  • 在reader方法中,读取flag(3)操作不能被重排序到读取a(4)之前。因为flag是volatile,在读flag的时候会插入一个LoadLoad屏障,确保在flag被读取之后,所有之前的读操作都完成了。

三、锁

1. 悲观锁和乐观锁
(1)悲观锁:认为访问共享资源时一定会发生线程安全问题,因此在访问时必须加锁。
(2)乐观锁:认为访问共享资源时不一定会发生线程安全问题,因此不会加锁,当提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改即可。
(3)二者区别

特性悲观锁乐观锁
锁定机制操作数据前先加锁,其他线程无法操作操作数据时不加锁,提交时检查冲突
使用场景适用于写多读少,冲突概率高的场景适用于读多写少,冲突概率低的场景
性能由于频繁加锁和释放锁,性能较低不加锁,性能较高
实现方式使用数据库锁机制(如行锁、表锁)使用版本号或时间戳进行冲突检测
数据一致性一致性强,通过锁机制防止并发修改一致性依赖于冲突检测和重试机制
并发性并发性差,锁定会阻塞其他线程并发性好,线程可以同时进行操作
死锁风险存在死锁风险不存在死锁风险
典型应用行锁、表锁、页锁和意向锁(数据库)、ReentrantLock等版本控制系统、CAS(Compare and Swap)操作

2. synchronized 关键字
(1)介绍
synchronized 关键字是一种对象锁,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁。
(2)底层实现原理
① Monitor(对象监视器)

  • Monitor(监视器)是一种同步机制,用于管理多线程之间的互斥访问和并发控制。Monitor是JVM提供的,但是是基于c++实现的。
  • Monitor的结构
    • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
    • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
    • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

在这里插入图片描述
② synchronized关键字实现原理
线程获得锁需要使用对象(锁)关联monitor监视器,即获得monitor的持有权。

(3)使用方法

  • 同步方法:当一个线程执行同步方法时,它会自动获得该方法所属对象的锁。其他线程在获得该锁之前无法执行该方法。
public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
  • 同步块:同步块用于在一个方法内同步部分代码,提供了比同步方法更细粒度的控制。
public class SynchronizedBlockExample {
    private final Object lock = new Object();
    private int count = 0;

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}
  • 静态同步方法:静态同步方法锁定的是类对象,对于所有该类的实例都有效。
public class StaticSynchronizedExample {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }

    public static synchronized int getCount() {
        return count;
    }
}

3. synchronized锁升级

Monitor实现的锁属于重量级锁,因为monitor是使用c++实现的,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。在JDK 1.6引入了两种新型锁机制:偏向锁轻量级锁

(1)对象的内存结构
在这里插入图片描述
:填充为8的整数倍是方便字节对齐提高程序运行效率 ,而且对于压缩指针也更加的方便。

(2)轻量级锁
在无竞争的情况下,轻量级锁使用CAS操作来实现锁的获取和释放,提高运行效率。
① 加锁流程

  • 在线程栈中创建一个Lock Record,将其obj字段指向锁对象。
  • 通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
  • 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。(仍需CAS操作)
  • 如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

② 解锁过程

  • 遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。
  • 如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。
  • 如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。

(3)偏向锁
与轻量级锁不同的是,偏向锁只第一次获得锁时使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后该线程再获取锁时,只需要判断锁对象mark word中是否是自己的线程id即可。

:轻量级锁和偏向锁都是在没有竞争的情况下使用,一旦发生了竞争会立刻升级为重量级锁。

4. Lock接口:ReentrantLock

Lock接口提供了一种比传统的同步块和方法更灵活的锁机制。Lock 接口有多种实现,其中最常用的是 ReentrantLock。

(1)Lock接口常见实现类

实现类描述
ReentrantLock可重入的互斥锁,具有与使用 synchronized 方法和语句相同的基本行为和语义,但功能更强大。
ReentrantReadWriteLock.ReadLock读锁,可被多个读线程同时持有,只要没有写线程持有写锁。
ReentrantReadWriteLock.WriteLock写锁,独占锁,只有写线程可以持有,读线程和其他写线程都被阻塞。
StampedLock一种新的读写锁实现,支持乐观读锁和写锁的互斥锁操作,提高并发性和性能。
LockSupport提供了基本的线程阻塞原语,用于创建锁和其他同步工具。

(2)ReentrantLock
① ReentrantLock是一个可重入且独占式的锁,还支持轮询、超时、中断、公平锁和非公平锁等。
② 特点

  • 可重入性:一个线程可以多次获得该锁,而不会导致死锁。这意味着同一个线程可以进入锁保护的代码块多次,必须确保在离开代码块时相应的次数解锁。
  • 公平性:可以选择公平锁或者非公平锁。公平锁按照线程请求的顺序获取锁,非公平锁则没有这种顺序,可能导致某些线程长时间等待。
  • 灵活性:提供了比 synchronized 块更灵活的锁获取方式,如可中断的锁获取、超时的锁获取。

③ 实现原理

  • ReentrantLock使用CAS+AQS来实现,使用用一个整数state表示锁的状态,使用一个先进先出的队列(CLH)管理被阻塞的线程。
  • 当线程需要获得锁时,使用CAS操作修改锁的状态,修改成功则获得锁;修改失败则加入CLH队列管理。

④ 常用方法

方法描述
lock()获取锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获得锁。
unlock()释放锁。
tryLock()尝试获取锁。如果锁在调用时未被另一个线程持有,则获取锁并立即返回 true;否则返回 false
tryLock(long time, TimeUnit unit)尝试在给定的等待时间内获取锁。如果锁在给定时间内可用,则获取锁并返回 true;否则返回 false
lockInterruptibly()如果当前线程未被中断,则获取锁。如果锁不可用,则当前线程出于线程调度目的将被禁用并处于休眠状态,直到发生以下两种情况之一:锁由当前线程获得;或者其他某个线程中断当前线程。
newCondition()返回一个绑定到此 Lock 实例的新 Condition 实例。

5. synchronized与ReentrantLock的比较

特性synchronizedReentrantLock
锁类型隐式锁显式锁
获取锁的方式自动需要显式调用 lock() 方法
释放锁的方式自动需要显式调用 unlock() 方法
可重入性
公平锁支持是(通过构造函数可以指定)
条件变量支持是(通过 newCondition() 方法)
获取锁中断是(通过 lockInterruptibly() 方法)
超时锁获取是(通过 tryLock(long time, TimeUnit unit) 方法)
性能开销较低(JVM 内部优化)较高
锁的范围代码块或方法灵活,可锁代码块
代码复杂度较高
推荐使用场景简单的同步需求复杂的同步需求,如需要公平锁、超时等

6. AQS(Abstract Queued Synchronizer,抽象队列同步器)
(1)介绍
AQS是Java 并发包 (java.util.concurrent.locks) 中的一个核心框架,是构建各种锁和同步组件的基础框架。
(2)实现原理

  • AQS 使用一个 int 类型的变量 state 表示同步状态。
  • AQS 内部维护了一个 FIFO 队列,用于管理获取锁失败的线程。线程在争夺锁失败后会被封装成队列节点(Node)并加入到等待队列中。
  • 队列中每个节点包含线程引用和状态标志

(3)常用方法

方法描述
acquire(int arg)独占模式获取锁,如果获取失败则进入等待队列。
release(int arg)独占模式释放锁,如果释放成功则唤醒等待队列中的下一个节点。
acquireShared(int arg)共享模式获取锁,如果获取失败则进入等待队列。
releaseShared(int arg)共享模式释放锁,如果释放成功则唤醒等待队列中的下一个节点。
addWaiter(Node mode)将当前线程封装成节点并加入等待队列的尾部。
unparkSuccessor(Node node)唤醒等待队列中的下一个节点。

(4)常见实现类

实现类描述
ReentrantLock可重入独占锁。允许线程重复获取同一把锁。
ReentrantReadWriteLock可重入读写锁。支持多个线程同时读取数据,但写操作是互斥的。
Semaphore信号量。控制同时访问特定资源的线程数。
CountDownLatch倒计时闩。一个或多个线程等待其他线程完成操作后再继续执行。
CyclicBarrier循环栅栏。一组线程相互等待,达到同步点后再继续执行。
Phaser阶段器。管理多个参与者线程的同步,支持动态注册和注销。
StampedLock读写锁的改进版本,支持乐观读取和写锁。
Condition条件变量。与锁配合使用,允许线程等待某个条件变为真。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发HarmonyOS NEXT (三) 熟悉ArkTs (上)

一、自定义组件 1、自定义组件 自定义组件,最基础的结构如下: Component struct Header {build() {} } 提取头部标题部分的代码,写成自定义组件。 1、新建ArkTs文件,把Header内容写好。 2、在需要用到的地方,导入…

C语言入门基础题:奇偶 ASCII 值判断(C语言版)和ASCII码表,什么是ASCII码,它的特点和应用?

1.题目描述: 任意输入一个字符,判断其 ASCII 是否是奇数,若是,输出 YES ,否则,输出 NO例如,字符 A 的 ASCI 值是 65 ,则输出 YES ,若输入字符 B(ASCII 值是 66)&#xff…

C# 异步编程Invoke、beginInvoke、endInvoke的用法和作用

C# 异步编程Invoke、beginInvoke、endInvoke的用法和作用 一、Invoke Invoke的本质只是一个方法,方法一定是要通过对象来调用的。 一般来说,Invoke其实用法只有两种情况: Control的Invoke Delegate的Invoke 也就是说,Invoke前…

C++初探究(2)

引用 对于一个常量,想要将其进行引用,则使用普通的引用相当于权限扩大(常量为只读,但此处的引用参数为可读可写),C编译器会报错. 例如: const int a 10;int& ra a;//权限放大&#xff0…

SDIO CMD 数据部分 CRC 计算规则

使用的在线 crc 计算工具网址:http://www.ip33.com/crc.html CMD CRC7 计算 如下图为使用逻辑分析仪获取的SDIO读写SD卡时,CMD16指令发送的格式,通过逻辑分析仪总线分析,可以看到,该部分的CRC7校验值得0x05,大多数情况…

说说iOS苹果的“开发者模式”什么时候需要打开 需不需要提前打开

在 iOS 开发过程中,开发者模式(Developer Mode)是一个非常重要的功能,它允许开发者在设备上运行和调试自己的应用程序。 经常有人私信或在群里问到,“我没有开发者模式,怎么办”,“开发者模式是…

4:表单和通用视图

表单和通用视图 1、编写一个简单的表单&#xff08;1&#xff09;更新polls/detail.html文件 使其包含一个html < form > 元素&#xff08;2&#xff09;创建一个Django视图来处理提交的数据&#xff08;3&#xff09;当有人对 Question 进行投票后&#xff0c;vote()视图…

Github绑定自己的域名

Github绑定自己的域名 1.注册自己的域名2.在GitHUb上创建一个自己的仓库&#xff0c;添加域名2.1 创建仓库2.2 添加域名2.3 在Setting中将域名添加到Custom domain中 3.添加域名解析获取ip地址4.在阿里云修改域名解析记录5.ping 域名即可成功 详细内容可参该博客&#xff1a; …

AutoHotKey自动热键(五)添加WINDOWS秘笈指令-输入瞬间启动功能

在AUTOHOTKEY的使用中,不仅仅可以监听组合热键,还可以监听正常文本击键录入,这是另一种监听方式,比如依次击键jsq之后直接弹出<计算器>工具,或者依次击键sj之后直接输出135****5564的手机号码,等等,这就是autohotkey的录入击键监听,以双冒号为开头:: 因这种录入监听像极了…

Word文件打开密码设置:掌握这两种方法,保护你的文档安全

在日常工作和学习中&#xff0c;我们经常会使用Microsoft Word来创建和编辑文档。有时候&#xff0c;为了保护文档内容不被未经授权的人员查看或修改&#xff0c;我们通常会采用加密的方式来增加其安全性。那么Word文档怎么加密&#xff1f; 方法一&#xff1a;使用Word软件内置…

CTF php RCE (一)

0x01 引言 首先进入题目 应该是大部分都是一段白盒PHP审计&#xff0c;然后我们为了命令执行&#xff0c;绕过或者是钻空子等等操作&#xff0c;来拿到flag 0x02 基础 0x01 传参方式 这里有两个工具&#xff0c;hackbar和burpsuite,这两个工具非常实用 大家可以自己Googl…

力扣第一题

1. 两数之和 提示 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可…

OpenFWI代码

重点关注文章第4部分 一、代码模块概览 这一部分了解代码主要实现的功能有哪些。 二、运行 这一部分关注如何跑通。 三、数据集 12个数据集&#xff08;11个2D1个3D&#xff09; 对计算机而言&#xff0c;上述输入、输出维度大小是按次数定义的。 以“Vel&#xff0c;F…

javaweb个人主页设计(html+css+js)

目录 1 前言和要求 1.1 前言 1.2 设计要求 2 预览 2.1 主页页面 2.2 个人简介 2.3 个人爱好 2.4 个人成绩有代码&#xff0c;但是图片已省略&#xff0c;可以根据自己情况添加 2.5 收藏夹 3 代码实现 3.1 主页 3.2 个人简介 3.3 个人爱好 3.4 个人成绩&#xff…

图吧工具箱:装机爱好者必备工具合集

名人说:莫道谗言如浪深,莫言迁客似沙沉。 ——刘禹锡《浪淘沙》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、概述二、主要功能1、硬件检测2、测试与故障诊断三、使用方法四、总结很高兴你打开了这篇博客,更多好用的软件工具,请关注我、订阅专栏…

0302GPIO外设输入功能

GPIO外设输入功能 输入部分硬件电路按键简介传感器模块简介按键和传感器模块的硬件电路 C语言的学习C语言数据类型宏定义typedef结构体枚举C语言知识总结 按键控制LED灯&光敏传感器蜂鸣器GPIO总结GPIO使用方法总结模块化编程的方法&#xff1a; 两个程序&#xff1a;按键控…

图像分割-编码解码网络的训练-kreas实现

一、数据集加载&#xff1a; 纯手工打造一个函数用来加载数据&#xff0c;数据分别为image和mask 我们所需要的类型&#xff1a;&#xff08;B数量&#xff0c;长&#xff0c;宽&#xff0c;通道数&#xff09;&#xff0c;只要将数据加载成这样并mask和image对应就行。 第一步…

S271系列RTU在旅游景区人流监控中的应用案例

S271系列RTU在旅游景区人流监控中的应用案例 随着全球旅游业的迅猛发展&#xff0c;旅游景区的管理者越来越关注如何利用先进的技术手段提升游客体验、优化管理效率以及确保安全。S271系列RTU作为一款先进的无线工业物联网设备&#xff0c;在旅游景区的人流监控中展现出了其独…

PTA - 编写函数计算圆面积

题目描述&#xff1a; 1.要求编写函数getCircleArea(r)计算给定半径r的圆面积&#xff0c;函数返回圆的面积。 2.要求编写函数get_rList(n) 输入n个值放入列表并将列表返回 函数接口定义&#xff1a; getCircleArea(r); get_rList(n); 传入的参数r表示圆的半径&#xff0c…

批量下载手机中APP程序中文件

需求 利用 adb pull 下载手机中app的某目录 adb pull 命令本身不支持直接下载整个目录&#xff08;文件夹&#xff09;及其所有子目录和文件作为一个单一的操作。但是&#xff0c;可以通过一些方法来间接实现这一目的。 方法 1. 首先将要下载的目录进行 tar 打包 # 在 And…