Java并发 - Java中所有的锁

news2024/9/24 1:21:35

Java 中提供了多种锁机制,用于实现多线程之间的同步和互斥。
在这里插入图片描述

1. 乐观锁&悲观锁

1.1 特点

乐观锁:假定多个事务之间很少发生冲突,操作不加锁。发生错误的时候进行回滚或重试。

悲观锁:假定冲突可能频繁发生,先加锁,阻止其他事务发生,操作后释放锁。

实现机制

乐观锁:实现方式是利用版本号(versioning)或时间戳(Timestamp),在进行更新的时候检查版本号或时间戳是否仍然匹配。

悲观锁:使用传统的锁机制,如synchronize 关键字或 ReetrantLock,执行完成释放锁

1.2 性能
  • 乐观锁:适合读多写少,在高并发写入的情况下,可能出现多次重试,导致性能降低。

  • 悲观锁:时候写多读少,在高并发写入的情况下,可能会有较多的线程竞争,导致性能降低。

1.3 示例

乐观锁:

AtomicInteger version = new AtomicInteger();

public void updateData(Object newData){
    int currentVersion = version.get();
    if (currentVersion == version.get()){
        // 执行更新
        update(newData);
        // 增加版本
        version.incrementAndGet();
    }else {
        throw new OptimisticLockingFailureException("concurrent modification detected");
    }
}

悲观锁:

// 使用ReentrantLock实现悲观锁
ReentrantLock lock = new ReentrantLock();

// 更新操作
public void updateData(Object newData) {
    lock.lock(); // 获取锁
    try {
        // 执行更新
        update(newData);
    } finally {
        lock.unlock(); // 释放锁
    }
}

2. 自旋锁 & 适应性自旋锁

自旋锁(Spin Lock)和适应性自旋锁是两种不同的锁实现,用于在多线程环境中同步访问共享资源。

2.1 特点

自旋锁:

  • 一直自旋,当线程尝试获取锁,如锁已被其他线程占用,一直自旋等待直到获取到锁为止。
  • 不考虑等待时间。

适应性自旋锁:

  • 动态自适应,根据锁的历史信息进行动态调整自旋时间。
  • 考虑等待时间。

优点:

自旋锁:低开销,自旋锁通常比较轻量,适用于锁竞争不激烈的情况。

适应性自旋锁:适应性 ,适应性自旋锁能够在锁的竞争激烈时自适应地减少自旋等待时间,提高效率。

缺点:

自旋锁:高竞争下效率低,在锁竞争激烈的情况下,自旋会导致线程不断自旋,浪费 CPU 资源。

适应性自旋锁:复杂性 ,实现适应性自旋锁的算法较为复杂,可能会引入一些额外的开销。

2.2 性能
  • 自旋锁: 适用于锁竞争不激烈的情况,且期望锁的开销较小。

  • 适应性自旋锁:适用于锁的竞争激烈、存在较多争用的情况,能够根据实际情况调整自旋等待时间。

2.3 示例

自旋锁

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();

    public void lock() {
        Thread currentThread = Thread.currentThread();
        while (!owner.compareAndSet(null, currentThread)) {
            // 自旋等待
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        owner.compareAndSet(currentThread, null);
    }
}

适应性自旋锁

见 JDK 并发包中的 ReentrantLock

3. 无锁 & 偏向锁 & 轻量级锁 & 重量级锁

这四种锁结构都是针对synchronized来说的。锁的升级是由于竞争的激烈程度导致的,竞争越大,锁结构越重量化。

锁升级实现流程实现
在这里插入图片描述

3.1 特点

无锁: 不使用任何锁机制,通过一些算法或硬件原语实现多线程间的同步,通常在并发度较高的情况下表现优越。

偏向锁: 当一个线程访问同步块并获取锁时,会在对象头上的Mark Word中记录锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行额外的操作。

轻量级锁: 当一个线程尝试获取锁时,如果没有竞争,将在对象头的Mark Word中存储指向锁记录的指针;如果有竞争,会膨胀为重量级锁。

重量级锁: 当一个线程尝试获取锁时,如果存在竞争,将通过操作系统的互斥原语(如操作系统的 Mutex)来实现锁。

优点:

无锁:无竞争开销: 适用于低竞争、高并发的情况,减少锁的开销。

偏向锁:减少竞争: 适用于大多数情况下都是单线程访问同步块的场景,减少无谓的竞争。

轻量级锁:低竞争: 适用于线程间竞争不激烈的情况,减少了传统重量级锁的性能开销。

重量级锁:适应高竞争: 在多线程间存在激烈竞争的情况下,重量级锁能够确保线程安全。

缺点:

无锁:复杂性: 无锁算法相对复杂,实现较为困难。

偏向锁:切换成本: 如果存在多线程竞争,会导致锁膨胀为轻量级锁,引入额外的切换成本。

轻量级锁:自旋等待: 线程可能会进行短暂的自旋等待,一旦竞争激烈,可能会升级为重量级锁。

重量级锁:性能开销: 在竞争激烈的情况下,频繁地切换锁的所有者,可能导致性能下降。

3.2 性能
  • 无锁: 适用于高并发度、低竞争的场景,算法实现较为复杂。
  • 偏向锁: 适用于单线程频繁访问同步块的场景,减少无谓的竞争。
  • 轻量级锁: 适用于线程间竞争不激烈的情况,减少了传统重量级锁的性能开销。
  • 重量级锁: 适用于多线程竞争激烈、需要确保线程安全的场景,但可能会引入较大的性能开销。

4. 公平锁 & 非公平锁

锁可以分为公平锁和非公平锁,它们主要区别于获取锁的顺序。

公平锁 / 非公平锁:

前提:已存在多个线程在排队获取锁,存在阻塞队列。

条件:新来一个线程需要获取资源。

公平锁:在阻塞队列后面排队。

非公平锁:先竞争锁,竞争失败了才到阻塞队列后面排队。

4.1 特点
  • 公平锁

    • 获取锁顺序: 公平锁按照请求锁的顺序获取锁,即先到先得的原则。
    • 等待队列: 线程在等待锁时会进入一个先进先出(FIFO)的等待队列。
    • 实现机制: 公平锁通常使用队列(如 ReentrantLock 中的 FairSync)来维护等待队列。
  • 非公平锁

    • 获取锁顺序: 非公平锁在尝试获取锁时不考虑等待队列中其他线程的顺序,可能会插队成功获取锁。
    • 等待队列: 线程在等待锁时可能会插队直接获取锁,不一定按照请求的顺序。
    • 实现机制: 非公平锁通常使用一种较为简单的机制,避免了公平锁的性能开销。

优点

公平锁:公平性: 所有线程都有机会获取锁,不会出现饥饿现象。

非公平锁:性能: 在高并发的情况下,非公平锁通常具有更好的吞吐量,因为它允许线程插队。

缺点

公平锁:性能开销: 实现公平锁通常需要维护一个等待队列,可能引入额外的性能开销。

非公平锁:可能导致饥饿: 一些线程可能会被其他线程一直插队,导致某些线程一直无法获取锁,可能会出现饥饿现象。

4.2 性能
  • 公平锁: 由于需要维护等待队列,可能在高并发场景下性能相对较低。
  • 非公平锁: 通常具有更好的吞吐量,因为它允许一些线程插队,降低了竞争。
4.3 示例
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁

5. 可重入锁 & 非可重入锁

锁可以分为可重入锁和非可重入锁,它们主要区别于同一个线程是否可以多次获得同一个锁。

ReentrantLocksynchronized 关键字都是可重入锁的实现。

5.1 特点
  • 可重入锁

    • 可重入性: 同一个线程可以多次获得同一个锁,每次获得锁都会给锁计数器加1,需要相同次数的释放操作才能释放锁。
    • 递归调用: 在递归调用中,线程不会被自己持有的锁所阻塞。
    • 实现机制: Java 中的 ReentrantLocksynchronized 关键字都是可重入锁的实现。
  • 非可重入锁

    • 不可重入性: 同一个线程获得锁后,再次尝试获得时会被阻塞,造成死锁。
    • 不支持递归调用: 在递归调用中,线程会被自己持有的锁所阻塞,无法再次获取。

6. 独享锁(排他锁) & 共享锁

锁可以分为独享锁(排他锁)和共享锁,它们主要用于多线程之间对共享资源的访问控制

6.1 特点
  • 独享锁

    • 获取方式: 独享锁是一种独占式的锁,一次只能被一个线程持有。
    • 互斥性: 当一个线程持有独享锁时,其他线程无法获取该锁,需要等待释放。
    • 操作原子性: 独享锁通常用于保护临界区或关键代码段,确保操作的原子性。
  • 共享锁

    • 获取方式: 共享锁是一种共享式的锁,可以被多个线程同时持有。
    • 允许并发: 多个线程可以同时获取共享锁,允许并发读取共享资源。
    • 读写分离: 共享锁通常用于读多写少的场景,提高读操作的并发性。

优点

独享锁:数据安全: 适用于需要保护临界区或写操作的场景,确保数据的一致性和完整性。

共享锁:高并发: 适用于读操作较多的场景,能够提高系统的并发性。

缺点

独享锁:竞争激烈: 在高并发场景下,独享锁的竞争可能较为激烈,导致性能下降。

共享锁:不适用于写操作: 对于写操作,需要等待其他线程释放共享锁,可能导致写操作的延迟。

6.2 性能
  • 独享锁: 适用于需要保护临界区或关键代码段,确保操作的原子性,写操作较为频繁的场景。
  • 共享锁: 适用于读操作较为频繁的场景,提高系统的并发性,对数据的一致性要求相对较低。
6.3 示例
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

// 共享锁
public int readData() {
    readWriteLock.readLock().lock(); // 获取读锁
    try {
        System.out.println(Thread.currentThread().getName() + " is reading data: " + data);
        return data;
    } finally {
        readWriteLock.readLock().unlock(); // 释放读锁
    }
}

// 独享锁
public void writeData(int newData) {
    readWriteLock.writeLock().lock(); // 获取写锁
    try {
        System.out.println(Thread.currentThread().getName() + " is writing data: " + newData);
        data = newData;
    } finally {
        readWriteLock.writeLock().unlock(); // 释放写锁
    }
}


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

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

相关文章

放大镜Scratch-第14届蓝桥杯Scratch省赛真题第3题

3. 放大镜&#xff08;50分&#xff09; 评判标准&#xff1a; 10分&#xff1a;满足"具体要求"中的1&#xff09;&#xff1b; 15分&#xff1a;满足"具体要求"中的2&#xff09;&#xff1b; 25分&#xff0c;满足"具体要求"中的3&#xff…

Cadence Editor 关于画PCB相关内容

目录 一 新建PCB文件 二 指定封装库 三 导入网表 四 放置器件 五 绘制板框 六 精准定位 七 原理图与PCB的交互 八 飞线设置 九 层管理 布局布线阶段需要显示的层 十 器件位置相关 1 器件选取的基准点 2 旋转 3 对齐 4 把器件移动到底层或顶层 5 锁定与解锁 6…

C# Entity Framework 中不同的数据的加载方式

延迟加载 延迟加载是指在访问导航属性时&#xff0c;Entity Framework 会自动查询数据库并加载相关数据。这种方式在我们需要访问导航属性时比较方便&#xff0c;因为我们无需手动加载相关数据&#xff0c;而且只会在需要时才会进行查询&#xff0c;从而减少了不必要的开销。但…

Qt6入门教程 4:Qt Creator常用技巧

在上一篇Qt6入门教程 3&#xff1a;创建Hello World项目中&#xff0c;通过创建一个Qt项目&#xff0c;对Qt Creator已经有了比较直观的认识&#xff0c;本文将介绍它的一些常用技巧。 Qt Creator启动后默认显示欢迎页面 创建项目已经用过了&#xff0c;打开项目也很简单&#…

C语言中关于函数调用的理解

理论 关于函数调用的方式有两类&#xff1a;传值调用和传址调用 传值调用&#xff1a;函数的形参和实参分别占有不同的内存块&#xff0c;对形参的修改不会影响实参。 传址调用&#xff1a;把函数外部创建变量的内存地址传递给函数参数的一种调用方式。可以让函数和函数外面…

基于 STM32 的 MPU6050 姿态计算方法及应用

基于STM32的MPU6050姿态计算方法可以通过融合陀螺仪和加速度计的数据来实现。在本文中&#xff0c;我们将介绍通过MPU6050获取姿态数据&#xff0c;并结合姿态解算算法实现姿态估计的方法&#xff0c;并提供相应的代码示例。 1. 硬件连接及库配置 首先&#xff0c;我们需要将…

uniapp中uview组件库CircleProgress 圆形进度条丰富的使用方法

目录 #内部实现 #平台差异说明 #基本使用 #设置圆环的动画时间 #API #Props 展示操作或任务的当前进度&#xff0c;比如上传文件&#xff0c;是一个圆形的进度环。 #内部实现 组件内部通过canvas实现&#xff0c;有更好的性能和通用性。 #平台差异说明 AppH5微信小程…

静态网页课程设计——贾斯汀比伯歌手介绍(HTML+CSS+JavaScript)

前言 使用技术&#xff1a;HTMLCSSJS 主要内容&#xff1a;对贾斯汀比伯这名歌手进行介绍&#xff0c;还有他的几首比较经典的歌曲以及视频观看。 简单介绍 本网站是以介绍贾斯汀比伯为主题而展开&#xff0c;主要由首页、《Changes》和音乐视频组成的。首页下面的是对他介绍…

数据在内存中的存储方式

前言&#xff1a; 期末临近&#xff0c;继续复习&#xff01; 今天要复习的内容是数据在内存中的存储&#xff0c;主要是整型与浮点两种&#xff0c;还有大小端的介绍。 提出问题 打印结果是255 -1 为什么&#xff1f; 首先我们要知道数据都是以二进制的形式存…

智能的核心依然是哲学的三个基本问题

智能的发展与哲学的三个基本问题密切相关&#xff0c;作为一个复杂领域&#xff0c;智能涉及到人类认知和行为的模拟与复制&#xff0c;因而也会涉及到哲学的核心问题。 存在论&#xff1a;智能的存在论问题涉及到什么是智能以及智能系统的本质。这包括对于意识、思维和自主性的…

酷雷曼精彩亮相CMC 2023中国元宇宙大会,助力云上VR直播

12月23日&#xff0c;2023中关村论坛系列活动——CMC 2023中国元宇宙大会在石景山首钢园冰壶馆成功举办。酷雷曼VR作为元宇宙领域代表企业之一受邀出席会议&#xff0c;分享元宇宙技术研发成果及应用方案&#xff0c;并为大会提供VR直播技术支持。 大咖云集&#xff0c;共商元宇…

js逆向第13例:猿人学第6题js混淆-回溯赛

文章目录 m是加密字符串怎么来的?浏览器环境检测本地运行的js代码任务六:采集全部5页的彩票数据,计算全部中奖的总金额(包含一、二、三等奖) 此题总体难度低于第5题,老规矩还是查看控制台请求地址https://match.yuanrenxue.cn/api/match/6?m=rPRDgpbV3Wd%252FyPfURQAkxK…

Vue知识总结-中

VUE-生命周期 生命周期概述 生命周期也常常被称为生命周期回调函数/生命周期函数/生命周期钩子生命周期是Vue在关键时刻帮我们调用的一些特殊名称的函数生命周期函数的名字不能更改,但函数的具体内容是由我们程序员自己编写的生命周期函数中的this指向是vm或组件实例对象 生命周…

[VUE]5-TypeScript

目录 1 TypeScript 介绍2、安装3、快速上手4、TypeScript 常用类型4.1 类型标注的位置4.2 字符串、数字、布尔类型4.3 字面量类型4.4 ⭐interface 类型4.5 class 类型 ​&#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;…

【Leetcode】移除后集合的最多元素数

目录 &#x1f4a1;题目描述 &#x1f4a1;思路 &#x1f4a1;总结 100150. 移除后集合的最多元素数 &#x1f4a1;题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素&#xff0c;同时从 …

江科大STM32

目录 STM32简介 STM32简介 我们主要学习的就是STM32的外设。 NVIC&#xff1a;内核里面用于管理中断的设备&#xff0c;比如配置中断优先级这些东西SysTick&#xff1a;内核里面的定时器&#xff0c;主要用来给操作系统提供定时服务的&#xff0c;STM32是可以加入操作系统的&am…

SSD固态硬盘数据修复方案介绍

这么多故障的可能&#xff0c;那么固态硬盘SSD的数据修复&#xff0c;到底是否有办法呢&#xff1f;我们这里介绍两种尝试修复的方式&#xff0c;不能保证一定会成功。在你误删除一些文件的时候&#xff0c;可以尝试下&#xff0c;市场也有也有很多的修复软件。 方式1:采用修复…

【软件测试】概述

《软件缺陷》 定义 &#xff08;从产品内部看&#xff09;软件产品开发或维护过程中存在的错误、毛病等各种问题&#xff08;从产品外部看&#xff09;系统所需要实现的某种功能的失效或违背 表现形式(1) 软件没有实现产品规格说明所要求的功能模块(2) 软件中出现了产品规格说…

[ffmpeg系列 03] 文件、流地址(视频)解码为YUV

一 代码 ffmpeg版本5.1.2&#xff0c;dll是&#xff1a;ffmpeg-5.1.2-full_build-shared。x64的。 文件、流地址对使用者来说是一样。 流地址(RTMP、HTTP-FLV、RTSP等)&#xff1a;信令完成后&#xff0c;才进行音视频传输。信令包括音视频格式、参数等协商。 接流的在实际…

day10 用栈实现队列 用队列实现栈

题目1&#xff1a;232 用栈实现队列 题目链接&#xff1a;232 用栈实现队列 题意 用两个栈实现先入先出队列&#xff08;一个入栈&#xff0c;一个出栈&#xff09;&#xff0c;实现如下功能&#xff1a; 1&#xff09;push&#xff1a;将元素x推到队列末尾 2&#xff09;…