几种常见锁的介绍

article/2025/3/17 13:02:33

在这里插入图片描述

参考链接:《这样来解释Java中的并发和锁,涨知识了》

    • 一、悲观锁与乐观锁
      • 1.1 悲观锁
      • 1.2 乐观锁
        • 简单探究一下CAS算法的底层
        • CPU级别的LOCK实现方式
      • 1.3 两种锁的使用场景
    • 二、独占锁与共享锁
      • 2.1 独占锁
      • 2.2 共享锁
    • 三、公平锁和非公平锁
      • 3.1 公平锁
      • 3.2 非公平锁
    • 四、可重入锁
    • 五、自旋锁
    • 六、分段锁
    • 七、锁升级
      • 7.1 轻量级锁和重量级锁的本质区别
      • 7.2 轻量级锁与重量级锁的适用场景
      • 7.3 偏向锁
    • 八、锁优化技术
      • 8.1 锁粗化
      • 8.2 锁消除

一、悲观锁与乐观锁

1.1 悲观锁

一个共享数据加了悲观锁,那线程每次想操作这个数据前都会假设其他线程也可能会操作这个数据,所以每次操作前都会上锁,这样其他线程想操作这个数据拿不到锁只能阻塞了。

在 Java 语言中 synchronizedReentrantLock等就是典型的悲观锁,还有一些使用了 synchronized 关键字的容器类如 HashTable 等也是悲观锁的应用。

1.2 乐观锁

乐观锁操作数据时不会上锁,在更新的时候会判断一下在此期间是否有其他线程去更新这个数据。

乐观锁可以使用版本号机制CAS算法实现。在 Java 语言中 java.util.concurrent.atomic包下的原子类就是使用CAS 乐观锁实现的。

简单探究一下CAS算法的底层

/*java代码*/
//AtomicInteger类
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}//Unsafe类,底层是C语言
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); //将Java对象解析成JVM的oop(普通对象指针), jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); //根据对象p和地址偏移量找到地址 return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //基于cas比较并替换, x表示需要更新的值,addr表示state在内存中的地址,e表示预期值 UNSAFE_END//atomic_linux_x86.inline.hpp
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {int mp = os::is_MP();__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" //is_MP判断是否多核,asm汇编: "=a" (exchange_value): "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp): "cc", "memory");return exchange_value;
}//最终实现的指令
lock cmpxchg //汇编指令

最终是一条汇编指令:lock cmpxchg,是英特尔CPU的一条汇编语言,本质是两步操作:一个是Lock,一个是CAS。单独的CAS算法其实不是原子性的,是不能作为锁使用。

CPU级别的LOCK实现方式

在这里插入图片描述

  1. 缓存锁,将原子操作放在cpu缓存中进行(L1、L2、L3高速缓存)
  2. 锁死数据总线,拉高北桥电平信号锁死数据总线
  3. 关中断,CPU在此中断处理完成前,不处理其它中断

1.3 两种锁的使用场景

乐观锁适用于写比较少(冲突比较小)的场景,因为不用上锁、释放锁,省去了锁的开销,从而提升了吞吐量。

如果是写多读少的场景,即冲突比较严重,线程间竞争激励,使用乐观锁就是导致线程不断进行重试,这样可能还降低了性能,这种场景下使用悲观锁就比较合适。

二、独占锁与共享锁

2.1 独占锁

独占锁是指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。

JDK中的synchronizedjava.util.concurrent(JUC)包中Lock的实现类就是独占锁。

互斥锁是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。互斥锁一次只能一个线程拥有互斥锁,其他线程只有等待。

2.2 共享锁

共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。

在 JDK 中 ReentrantReadWriteLock 就是一种共享锁。

读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,而写锁是独占的。写锁的优先级要高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。读写锁相比于互斥锁并发程度更高,每次只有一个写线程,但是同时可以有多个线程并发读。

三、公平锁和非公平锁

3.1 公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买,后来的人在队尾排着,这是公平的。

在 java 中,ReentrantLock可以通过构造函数初始化公平锁。

Lock lock = new ReentrantLock(true);public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

3.2 非公平锁

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的状态(某个线程一直得不到锁)。

在 java 中 synchronized 关键字是非公平锁,ReentrantLock默认也是非公平锁。

public ReentrantLock() {sync = new NonfairSync();
}

四、可重入锁

可重入锁又称之为递归锁,是指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。

对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁。对于Synchronized而言,也是一个可重入锁。

public synchronized void mehtodA() throws Exception{// Do some magic tingsmehtodB();
}public synchronized void mehtodB() throws Exception{// Do some magic tings
}

上面的代码中 methodA 调用 methodB,如果一个线程调用methodA 已经获取了锁再去调用 methodB 就不需要再次获取锁了,这就是可重入锁的特性。

如果不是可重入锁的话,mehtodB 可能不会被当前线程执行,可能造成死锁。

五、自旋锁

自旋锁是指线程在没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环就是所谓的自旋。

自旋锁的目的是为了减少线程被挂起的几率,因为线程的挂起和唤醒也都是耗资源的操作。

如果锁被另一个线程占用的时间比较长,即使自旋了之后当前线程还是会被挂起,忙循环就会变成浪费系统资源的操作,反而降低了整体性能。因此自旋锁是不适应锁占用时间长的并发情况的。

在 Java 中,AtomicInteger 类有自旋的操作:

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}//普通的读写无法保证有序性和可见性,所以Unsafe还提供了volatile读写,同样的也提供了基本类型与地址读写
public native int getIntVolatile(Object var1, long var2);

CAS 操作如果失败就会一直循环获取当前 value 值然后重试。

另外自适应自旋锁也需要了解一下。

JDK1.6又引入了自适应自旋,这个就比较智能了,自旋时间不再固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定。如果虚拟机认为这次自旋也很有可能再次成功那就会次序较多的时间,如果自旋很少成功,那以后可能就直接省略掉自旋过程,避免浪费处理器资源。

六、分段锁

分段锁 是一种锁的设计,并不是具体的一种锁。

分段锁设计目的是将锁的粒度进一步细化,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
在这里插入图片描述

七、锁升级

7.1 轻量级锁和重量级锁的本质区别

Java的线程和Go的协程

  1. jvm与操作系统的线程是按照1:1模型处理,jvm会将线程交给操作系统管理
  2. Go与操作系统的线程是按照M:N的模型,即协程,一般的M远大于N

对于JVM而言,其自己管理的线程调度,比如While循环处理,就是轻量级锁;如果交给操作系统,进入调度队列,则就是重量级锁。

7.2 轻量级锁与重量级锁的适用场景

轻度竞争的时候,即处理耗时短,使用轻量级。

重度竞争的时候,耗时长,如果使用轻量级,会导致CPU压力过大,使用重量级。

jdk1.6之后,自适应自旋,自动将轻量级升级为重量级。

7.3 偏向锁

偏向锁只是一种针对加锁操作的优化手段

偏向锁设计目的:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果。

偏向锁升级:通过记录线程id,保证同一线程再次执行的时候,可直接获取锁。如果发生轻度竞争,直接升级为轻量级锁,重度竞争升级为重量级锁。

偏向锁启动延时:由于JVM虚拟机自己有一些默认启动的线程,里面有很多的synchronized代码,这些syncchronized代码启动的时候必然会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级,效率很低。所以偏向锁有一个启动延时,默认是4秒,可以通过jvm参数进行设置。

-XX:BiasedLockingStartupDelay=0

八、锁优化技术

8.1 锁粗化

锁粗化就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。

for(int i = 0;i < 100; i++) {synchronized(this){// 业务逻辑}
}//优化后
synchronized(this) {for(int i = 0;i < 100; i++) {// 业务逻辑}
}

8.2 锁消除

锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。

比如,线程安全的StringBuffer

public String test(String s1, String s2){StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(s1);stringBuffer.append(s2);return stringBuffer.toString();
}

上面代码中有一个 test 方法,主要作用是将字符串 s1 和字符串 s2 串联起来。

test 方法中三个变量s1, s2, stringBuffer, 它们都是局部变量,局部变量是在栈上的,栈是线程私有的,所以就算有多个线程访问 test 方法也是线程安全的。

我们都知道 StringBuffer 是线程安全的类,append 方法是同步方法,但是 test 方法本来就是线程安全的,为了提升效率,虚拟机帮我们消除了这些同步锁,这个过程就被称为锁消除

编译器在编译的时候,进行逃逸分析。分析synchronized锁对象是不是只可能被一个线程加锁,不存在其他线程来竞争加锁的情况。这时就可以消除该锁了,提升执行效率。

@Override
public synchronized StringBuffer append(int i) {toStringCache = null;super.append(i);return this;
}//super父类是AbstractStringBuilder,为线程不安全的类

http://chatgpt.dhexx.cn/article/5ANDijda.shtml

相关文章

19-你知道哪几种锁?分别有什么特点?

首先会对锁的分类有一个整体的概念&#xff0c;了解锁究竟有哪些分类标准。 锁的 7 大分类 需要首先指出的是&#xff0c;这些多种多样的分类&#xff0c;是评价一个事物的多种标准&#xff0c;比如评价一个城市&#xff0c;标准有人口多少、经济发达与否、城市面积大小等。而…

mysql数据库锁有哪几种_Mysql数据库的分布式锁有哪几种?

原标题&#xff1a;Mysql数据库的分布式锁有哪几种&#xff1f; 文章内容 作者&#xff1a;jstu 文章来自&#xff1a;博客 链接&#xff1a;https://blog.csdn.net/lovexiaotaozi/article/details/83819916 你可能喜欢的文章 1Mysql性能优化一&#xff1a;SQL语句性能优化 悲观…

锁的分类和介绍

java中的各种锁详细介绍 转自&#xff1a;https://blog.csdn.net/axiaoboge/article/details/84335452 Java提供了种类丰富的锁&#xff0c;每种锁因其特性的不同&#xff0c;在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码&#xff08;本文中的源码来自JDK 8&a…

java线程锁有哪几种_java中常见的几种锁有哪些

公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序&#xff0c;并不是按照申请锁的顺序&#xff0c;有可能后申请的线程比先申请的线程优先获取锁&#xff0c;有可能&#xff0c;会造成优先级反转或者饥饿现象。 独享锁/共享锁 …

常见的各种锁总结

常见的各种锁 一、常见锁简单说明 1、悲观锁 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据&#xff0c;在获取数据的时候先加锁&#xff0c;确保数据的安全性。 锁实现&#xff1a;关键字synchronized、Lock接口的实现 使用场景&#xff1a;写操作比较多&#xff0…

BioGRID 互作数据库

01 — BioGRID BioGRID 是 Biological General Repository for Interactionh Datasets 的缩写&#xff08;网址为 https://thebiogrid.org&#xff09;&#xff0c;是一个公开的数据库&#xff0c;主要记录、整理包括蛋白、遗传和化学互作的数据&#xff0c;涵盖人类和所有主要…

Fuzzing论文:Reinforcement Learning-based Hierarchical Seed Scheduling for Greybox Fuzzing

Reinforcement Learning-based Hierarchical Seed Scheduling for Greybox Fuzzing 整体内容论文内容多级代码覆盖指标&#xff08;用于种子聚类&#xff09;分层种子调度策略实验 论文题目Reinforcement Learning-based Hierarchical Seed Scheduling for Greybox Fuzzing工具…

BIO的理解

前言 bio&#xff1a;b有两说&#xff0c;一为base&#xff0c;jdk中最早抽象出的io体系&#xff1b;一为block&#xff0c;jdk 1.0 中的io体系是阻塞的。所以两说皆有道理&#xff0c;一般我们认为b取block之意 nio&#xff1a;n也有两说&#xff0c;一为new&#xff0c;针对…

BIO初步学习

1.一个服务端一个客户端 具体代码实现 服务端 public class Server {public static void main(String[] args) {try {ServerSocket serverSocket new ServerSocket(9999);Socket socket serverSocket.accept();InputStream inputStream socket.getInputStream();Buffered…

12.BIO详解

Java BIO 就是传统的 java io 编程, 其相关的类和接口在 java.io 中. BIO 编程简单流程 服务器端启动一个 ServerSocket.客户端启动 Socket 对服务器进行通讯, 默认情况下服务器需要对每个客户建立一个县线程与之通讯.客户端发出请求后, 先咨询服务器是否有线程响应, 如果没有…

bioinformatics小技巧

文章目录 1. 软件安装1.1 linux上python2的安装1.2 Mercurial 安装及使用1.3 tRNAscan的安装和使用1.4 Linux上安装miniconda 2.数据下载2.1 linux上通过ftp下载一个文件夹下的全部文件2.2 GEO数据库数据下载 3.操作系统3.1 Windows下将R设置为环境变量。3.2 Linux 下怎样快速查…

brat标注的ann文件,转为BIO序列标注

这个地方真的好少有人写到&#xff0c;踩了好久的坑都不知道怎么解决。 首先&#xff0c;在用brat自带的转换序列标注的文件时&#xff0c;运行程序 1、python2 anntoconll.py ../data/data_new/corpoa.txt 报错&#xff1a; File "anntoconll.py", line 154, in…

BIO学习笔记

视频地址&#xff1a;https://www.bilibili.com/video/BV1gz4y1C7RK?fromsearch&seid15021234423448500976 2. JAVA BIO深入剖析 Java BIO 就是传统的 java io 编程&#xff0c;其相关的类和接口在 java.io。 BIO(blocking I/O) &#xff1a; 同步阻塞&#xff0c;服务器…

BIO实例

使用 BIO 模型编写一个服务器端&#xff0c;监听 6666 端口&#xff0c;当有客户端连接时&#xff0c;就启动一个线程与之通讯。要求使用线程池机制改善&#xff0c;可以连接多个客户端.服务器端可以接收客户端发送的数据(telnet 方式即可)。 图 代码演示&#xff1a; package…

利用 bioconda 管理生物信息软件

利用 bioconda 管理生物信息软件 如需视频讲解&#xff0c;请移步&#xff1a;一只小蛮要 【要知道 bioinfo】利用 bioconda 管理生物信息软件 1 了解 conda&#xff0c;anaconda&#xff0c;miniconda&#xff0c;bioconda 1.1 conda conda是一个软件模块管理工具&#xff…

IMex和IntAct数据库简介

欢迎关注微信公众号《生信修炼手册》! 蛋白质相互作用的数据库非常的多&#xff0c;比如DIP, MINT, IntAct, BioGRID等&#xff0c;不同数据库中的信息存在了大量的冗余&#xff0c;而且在不同数据库之间进行检索也非常的费力&#xff0c;为了减少不同数据库的冗余&#xff0c…

【Bio】基础生物学 - 基因 gene

文章目录 1. DNA 脱氧核糖核酸、RNA 核糖核酸1.1 核苷酸1.2 脱氧核糖核酸1.3 核糖核酸 2. 基因2.1 基因组2.2 染色体2.3 基因与脱氧核苷酸的牵连2.4 基因与DNA的牵连2.5 基因与染色体的牵连 Ref 1. DNA 脱氧核糖核酸、RNA 核糖核酸 1.1 核苷酸 核苷酸 (Nucleotide) \blue{\tex…

Bioedit 使用

1. 下载&#xff1a; 地址一搜就有&#xff0c;软件界面如下 BioEdit Download - Research software utility for creating and editing biological sequences 2. 酶切位点分析 构建过表达质粒、双荧光素酶质粒必用功能。以人的MYOD1基因为例 &#xff0c;分析酶切位点。 &…

BIO~~

BIO~~ 第一章 Java的I/O演进之路2.1 I/O 模型基本说明2.2 I/O模型Java BIOJava NIOJava AIO 2.3 BIO、NIO、AIO 适用场景分析 第三章 JAVA BIO深入剖析3.1 Java BIO 基本介绍3.2 Java BIO 工作机制3.3 传统的BIO编程实例回顾客户端案例如下服务端案例如下小结 3.4 BIO模式下多发…

python处理数据的一些代码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、如何读取txt文件&#xff0c;将其转化为DataFrame格式二、给DataFrame添加列名三、删除指定行四、读取csv文件&#xff0c;不让第一行成为列名五、读取DataFram…