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

news2024/12/23 10:58:29

本文对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.coloradmin.cn/o/1893789.html

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

相关文章

JAVA案例模拟电影信息系统

一案例要求: 二具体代码(需要在同一个包下创建三个类) Ⅰ:实现类 package 重修;import java.util.Random; import java.util.Scanner;public class first {public static void main(String[] args) {javabean[]moviesnew javabean[4];movies[0] new ja…

身边有填报志愿需求别错过!张雪峰透露今年志愿填报技巧:报专业,别报行业!(文末附稳定高薪专业推荐)

高考填报志愿是每个考生和家长都要面对的重大抉择。在当前就业形势日趋严峻、部分行业发展前景不明朗的大背景下,考生在填报志愿时更需要全面了解各个专业的就业前景,理性权衡自身兴趣特长与社会需求,而不是盲目跟风报考所谓的"热门专业"。 今天跟大家分…

航空数据管控系统-①项目准备阶段:任务1:项目需求描述

任务描述 从用户的角度阐述项目的开发背景、使用范围及功能需求,从而指导学生独立完成项目的设计与开发。 任务指导 目录 标题 内容 备注 1. 项目概述 1.1 项目背景介绍 (1)说明产品是什么,什么用途 (2&#xff…

KUKA机器人中断编程3—暂停功能的编程

在KUKA机器人的使用过程中,对于调试一个项目,当遇到特殊情况时需要暂停机器人,等异常情况处理完成后再继续机器人的程序运行。wait for指令是等待一个输入信号指令,没有输入信号,机器人一直等待。在一定程度上程序也不…

黑马点评DAY5|商户查询缓存

商户查询缓存 缓存的定义 缓存就是数据交换的缓冲区(Cache),是存储数据的临时地方,一般读写性能较高。 比如计算机的CPU计算速度非常快,但是需要先从内存中读取数据再放入CPU的寄存器中进行运算,这样会限…

67.WEB渗透测试-信息收集- WAF、框架组件识别(7)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:66.WEB渗透测试-信息收集- WAF、框架组件识别(6)-CSDN博客 关于w…

6年铲屎官测评宠物空气净化器哪款好,热门养宠空气净化器排名

作为一名资深猫奴,发现很多铲屎官每到春秋换季就开始疯狂打喷嚏、突然开始全身过敏。其原因是猫毛一到换季就开始疯狂掉毛,相对于可见猫毛,漂浮在空气中的浮毛就是罪灰祸首。微小的浮毛在空气总容易被人体吸入体内,而浮毛上面附带…

通义灵码AI编程助手怎么样?通义灵码详解(亲测)

通义灵码是阿里云推出的一款基于通义大模型的AI智能编程辅助工具,通义灵码提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码优化、注释生成、代码解释、研发智能问答、异常报错排查等能力,阿小云axiaoyun.com整理通义灵码介绍、使用场景、…

澳蓝荣耀时刻,6款产品入选2024年第一批《福州市名优产品目录》

近日,福州市工业和信息化局公布2024年第一批《福州市名优产品目录》,澳蓝自主研发生产的直接蒸发冷却空调、直接蒸发冷却组合式空调机组、间接蒸发冷水机组、高效间接蒸发冷却空调机、热泵式热回收型溶液调湿新风机组、防火湿帘6款产品成功入选。 以上新…

二叉树与堆相关的时间复杂度问题

目录 满二叉树与完全二叉树高度h和树中节点个数N的关系 向上调整算法: 介绍: 复杂度推导: 向下调整算法: 介绍: 复杂度推导: 向上调整建堆: 介绍: 复杂度推导:…

自然语言处理学习--3

对自然语言处理领域相关文献进行梳理和总结,对学习的文献进行梳理和学习记录。希望和感兴趣的小伙伴们一起学习。欢迎大家在评论区进行学习交流! 论文:《ChineseBERT: Chinese Pretraining Enhanced by Glyph and Pinyin Information》 下面…

sublime 3 背景和字体颜色修改

sublime 4 突然抽风,每次打开都显示 “plugin_host-3.3 has exited unexpectedly, some plugin functionality won’t be available until Sublime Text has been restarted” 一直没调好,所以我退回到sublime 3了。下载好了软件没问题,但是一…

基于opencv的斜光测距及python实现

1.前言 最近做了一个基于opencv的斜光测距的小项目,东西不多,但是很有意思,值得拿出来学一学。项目里面需要比较精确的定位功能,将前人matlab代码移植到python上,并且做了一些优化,简化逻辑(毕竟我是专业的…

Spring Boot中使用SpringEvent组件

Spring的事件机制是基于观察者模式的实现,主要由以下三个部分组成: 事件(Event):事件是应用中发生的重要事情,通常是一个继承自ApplicationEvent的类。 事件发布器(Publisher)&…

iPhone白苹果怎么修复?4个方法解决你的烦恼!

其实iPhone手机出现“白苹果”这事,如果是iPhone轻度用户,可能大家一辈子都不会遇到一次。但如果是iPhone重度用户、越狱爱好者、软件收集狂,可能就会遇到了。 白苹果,一般指iOS设备出现软、硬件故障,卡在一个类似于启…

AI智能对话绘画音乐三合一创作神器源码系统 带完整的源代码包以及搭建部署教程

系统概述 该系统旨在通过先进的AI技术,实现用户与机器的深度交互,从而在对话中创作出独一无二的艺术作品——无论是细腻的画作、动人的乐曲,还是两者兼备的多媒体体验,都能在这个平台上轻松实现。本文将详细介绍该系统的系统概述…

2024年在WordPress中创建销售活动的专家级优惠券方法

2024年在WordPress中创建销售活动的专家级优惠券方法 今天我想和大家分享一些关于如何在WordPress网站上使用专家级优惠券工具来创建销售活动的经验。对于已经在电商领域有一定经验的店主,利用专家级优惠券不仅能吸引顾客,还能显著增加销量。在这篇文章…

CLAM用于弱监督WSI分析

计算病理学(computational pathology)下的深度学习方法需要手动注释大型 WSI 数据集,并且通常存在领域适应性和可解释性较差的问题。作者报告了一种可解释的弱监督深度学习方法,只需要WSI级标签。将该方法命名为聚类约束注意力多实…

商务视频推广8个增加用户转化率的技巧-华媒舍

商务视频推广是一种有效的营销策略,可以帮助企业吸引更多的潜在客户并增加用户转化率。我们将介绍8个提高商务视频推广效果的技巧,帮助您更好地利用视频来促进业务增长。 技巧一:制作高质量的内容 成功的商务视频推广首先要有高质量的内容。…

达梦数据库 页大小与数据库字段长度的关系

对于达梦数据库实例而言,页大小 (page_size)、簇大小 (extent_size)、大小写敏感 (case_sensitive)、字符集 (charset) 这四个参数,一旦确定无法修改;如果过程中发现这些数据设置的不对,只能是重新新建数据库实例,而不…