JAVA中那些令人眼花缭乱的锁

news2024/12/29 12:49:08

一、开局一张图带你了解java相关的锁

Java中那些眼花缭乱的锁-2021-06-16-23-19-40二、乐观锁和悲观锁


1、悲观锁

悲观锁对应于生活中悲观的人,悲观的人总是想着事情往坏的方向发展。

举个生活中的例子,假设厕所只有一个坑位了,悲观锁上厕所会第一时间把门反锁上,这样其他人上厕所只能在门外等候,这种状态就是「阻塞」了。

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

20210606232504-2021-06-06-23-25-04

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

2、乐观锁

乐观锁 对应于生活中乐观的人,乐观的人总是想着事情往好的方向发展。

举个生活中的例子,假设厕所只有一个坑位了,乐观锁认为:这荒郊野外的,又没有什么人,不会有人抢我坑位的,每次关门上锁多浪费时间,还是不加锁好了。你看乐观锁就是天生乐观!

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

乐观锁可以使用版本号机制和CAS算法实现。

20210606232434-2021-06-06-23-24-35

在 Java 语言中 java.util.concurrent.atomic包下的原子类就是使用CAS 乐观锁实现的。

两种锁的使用场景

悲观锁和乐观锁没有孰优孰劣,有其各自适应的场景。

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

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

三、独占锁和共享锁


1、独占锁

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

20210606232544-2021-06-06-23-25-45

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

2、共享锁

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

20210606232612-2021-06-06-23-26-13在 JDK 中 ReentrantReadWriteLock 就是一种共享锁。

四、互斥锁和读写锁


1、互斥锁

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

20210606232634-2021-06-06-23-26-35

 互斥锁一次只能一个线程拥有互斥锁,其他线程只有等待。

2、读写锁

读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。

读锁可以在没有写锁的时候被多个线程同时持有,而写锁是独占的。写锁的优先级要高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。

读写锁相比于互斥锁并发程度更高,每次只有一个写线程,但是同时可以有多个线程并发读。

20210606232658-2021-06-06-23-26-59 在 JDK 中定义了一个读写锁的接口:ReadWriteLock

public interface ReadWriteLock {
    /**
     * 获取读锁
     */
    Lock readLock();
 
    /**
     * 获取写锁
     */
    Lock writeLock();
}

ReentrantReadWriteLock 实现了ReadWriteLock接口,具体实现这里不展开,后续会深入源码解析。

五、公平锁和非公平锁


1、公平锁

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

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

/**
* 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁
*/
Lock lock = new ReentrantLock(true);

2、非公平锁

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

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

/**
* 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁
*/
Lock lock = new ReentrantLock(false);

六、可重入锁


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

20210606232755-2021-06-06-23-27-56 对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁。对于Synchronized而言,也是一个可重入锁。

敲黑板:可重入锁的一个好处是可一定程度避免死锁。

以 synchronized 为例,看一下下面的代码:

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

上面的代码中 methodA 调用 methodB,如果一个线程调用methodA 已经获取了锁再去调用 methodB 就不需要再次获取锁了,这就是可重入锁的特性。如果不是可重入锁的话,mehtodB 可能不会被当前线程执行,可能造成死锁。

七、自旋锁


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

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

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

在 Java 中,AtomicInteger 类有自旋的操作,我们看一下代码:

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

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

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

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

分段锁


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

分段锁设计目的是将锁的粒度进一步细化,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。20210606232830-2021-06-06-23-28-31

 在 Java 语言中 CurrentHashMap 底层就用了分段锁,使用Segment,就可以进行并发使用了。

锁升级(无锁|偏向锁|轻量级锁|重量级锁)
JDK1.6 为了提升性能减少获得锁和释放锁所带来的消耗,引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

无锁

无锁状态其实就是上面讲的乐观锁,这里不再赘述。

偏向锁

Java偏向锁(Biased Locking)是指它会偏向于第一个访问锁的线程,如果在运行过程中,只有一个线程访问加锁的资源,不存在多线程竞争的情况,那么线程是不需要重复获取锁的,这种情况下,就会给线程加一个偏向锁。

偏向锁的实现是通过控制对象Mark Word的标志位来实现的,如果当前是可偏向状态,需要进一步判断对象头存储的线程 ID 是否与当前线程 ID 一致,如果一致直接进入。

轻量级锁

当线程竞争变得比较激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式等待上一个线程释放锁。

重量级锁

如果线程并发进一步加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁,重量级锁会使除了此时拥有锁的线程以外的线程都阻塞。

升级到重量级锁其实就是互斥锁了,一个线程拿到锁,其余线程都会处于阻塞等待状态。

在 Java 中,synchronized 关键字内部实现原理就是锁升级的过程:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。这一过程在后续讲解 synchronized 关键字的原理时会详细介绍。

八、锁优化技术(锁粗化、锁消除)


锁粗化

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

举个例子,一个循环体中有一个代码同步块,每次循环都会执行加锁解锁操作。

private static final Object LOCK = new Object();
 
for(int i = 0;i < 100; i++) {
    synchronized(LOCK){
        // do some magic things
    }
}

经过锁粗化后就变成下面这个样子了:

 synchronized(LOCK){
     for(int i = 0;i < 100; i++) {
        // do some magic things
    }
}

锁消除

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

举个例子让大家更好理解。

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 方法本来就是线程安全的,为了提升效率,虚拟机帮我们消除了这些同步锁,这个过程就被称为锁消除。

StringBuffer.class
// append 是同步方法

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

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

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

相关文章

python pyqtgraph绘图库

pyqtgraph官网 PyQtGraph被大量应用于Qt GUI平台&#xff08;通过PyQt或PySide&#xff09;&#xff0c;因为它的高性能图形和numpy可用于大量数据处理。 特别注意的是&#xff0c;pyqtgraph使用了Qt的GraphicsView框架&#xff0c;它本身是一个功能强大的图形系统; 我们将最优…

知识付费海哥:这样做课,不赚钱都难

现在不少人开始了开发网课&#xff0c;卖网课赚钱&#xff0c; 但是在网课开发时&#xff0c;很多人开始的时候&#xff0c;关注的点就错了&#xff01; 自己喜欢钓鱼&#xff0c;就开发钓鱼的课&#xff0c; 自己喜欢演讲&#xff0c;就开发演讲的课&#xff0c; 自己喜欢…

Dubbo入门(二)——IDEA下Dubbo+Zookeeper搭建

目录一、Zookeeper1.1 下载1.2 安装1.3 修改配置文件1.4 启动二、Dubbo插件搭建三、手动创建3.1 创建项目3.1.1 pom依赖3.2 api模块3.2.1 pom依赖3.2.2 实体类3.2.3 service接口3.3 provider3.3.1 pom依赖3.3.2 配置文件3.3.3 mapper3.3.4 service实现类3.3.5 启动类3.4 consum…

冶金工艺流程(钢铁全流程)

工艺图 工艺讲解 生产流程从矿石原料采集开始,对于低品位的矿石&#xff0c;必须在冶炼前经选矿工序先选出铁精矿&#xff0c;然后进一步制成烧结矿或球团矿。 ————————————————————————————— 高炉冶炼是一个连续的、大规模的高温生产过程。铁矿石…

为什么Aruba始终走在网络世界的前列?

编辑 | 阿冒 设计 | 沐由关于网络的价值&#xff0c;梅特卡夫定律&#xff08;Metcalfes law&#xff09;曾经在多年前就明白无误地告诉我们&#xff1a;VKN。其中&#xff0c;V代表一个网络的价值&#xff0c;N代表这个网络的节点数&#xff0c;K代表价值系数。如果梅特卡夫…

47. 批量规范化 代码从零开始实现 + 简洁实现

1. 从零实现 下面&#xff0c;我们从头开始实现一个具有张量的批量规范化层。 import torch from torch import nn from d2l import torch as d2l# X是输入&#xff0c;gamma, beta是两个可以学习的参数&#xff0c;moving_mean, moving_var是整个数据集的均值和方差&#xf…

ssh登录——scp传文件

一、ssh登录 1.基本用法 远程登录服务器&#xff1a; ssh userhostname# user: 用户名 # hostname: IP地址或域名 第一次登录时会提示&#xff1a; The authenticity of host 123.57.47.211 (123.57.47.211) cant be established. ECDSA key fingerprint is SHA256:iy237y…

十分钟掌握 “UML ” 的基本使用

十分钟掌握 “UML ” 的基本使用 每博一文案 很喜欢这样一段话&#xff1a;内可以不跟别人计较&#xff0c;但一定要学会维护自己&#xff0c;你可以不跟别人争抢&#xff0c;但应该懂得远离小人。 人生在世&#xff0c;我们会遇到形形色色的人&#xff0c;人心隔肚皮&#xf…

Pytorch深度学习实战——第3章课后习题

1.从list(range(9))list(range(9))list(range(9))中创建一个张量aaa并预测检查其大小、偏移量、步长。 import torch atorch.tensor(list(range(9))) print(a.storage_offset())#偏移量 print(a.stride())#步长 print(a.size())#大小a)使用ba.view(3,3)ba.view(3,3)ba.view(3,…

Aspose.PDF for Java系列3-创建复杂的PDF

创建复杂PDF 上篇文章展示了使用Java和Aspose.PDF简单步骤。本文中&#xff0c;我们将使用Java和Aspose.PDF创建更复杂的文档结构。此示例中&#xff0c;我们将创建包含图片、两个文本片段&#xff08;标题和段落&#xff09;和表格。 创建文档参考以下步骤&#xff1a; 实例…

被房地产坑惨的老板电器,终于要“回血”了!

近期&#xff0c;随着房地产行业重新被定义为“国民经济的支柱产业”&#xff0c;叠加疫情防控转向&#xff0c;笼罩在家电行业上空的阴霾逐渐消散&#xff0c;家电股预期改善&#xff0c;纷纷迎来估值修复。尤其是曾遭房地产暴击的老板电器&#xff0c;有望抓住房地产回暖的窗…

Synchronized

Synchronized前言1.synchronized修饰符和线程控制1.1synchronized修饰符和Object Monitor模式1.2 synchronized修饰符可标注的位置2. 方法2.1 waitwait多态表达式notify()方法和notifyAII()方法interrupt中断信号判断是否收到interrupt中断信号的方法前言 悲观锁在Java中有两种…

Jenkins环境搭建

Jenkins环境搭建 一、Jenkins下载&#xff1a;Jenkins 点击Download&#xff0c;进入新的页面后点击下载Jenkins的war包&#xff0c;如下图&#xff1a; 注意&#xff1a;Jenkins是java语言开发&#xff0c;需要安装JDK并进行环境变量配置&#xff0c;Jenkins可以在Tomcat容器…

java注解(Annotation)和反射(Reflection)

文章目录重要的点一 注解(Annotation)(Annotation)(Annotation)1.1 注解初识1.2 内置注解1.3 内置注解代码演示1.4 元注解(meta−annotation)(meta-annotation)(meta−annotation)1.5 元注解代码演示1.6 自定义注解1.7 自定义注解代码演示二 反射(Reflection)(Reflection)(Refl…

gitlab-ci.yml关键字(三)before_script 、script 、after_script

before_script before_script 用于定义在每个作业执行之前所运行的一系列脚本命令。这里需要注意的是&#xff0c;before_script 运行的时机&#xff0c;是在制品&#xff08;artifacts&#xff09;恢复后&#xff0c;也就意味着&#xff0c;在这个时机中是可以操作制品的。 …

Unity 解决QFramework WebGL报错

Unity 解决QFramework WebGL报错&#x1f95d;错误描述☕原因&#x1f364; 解决方案&#x1f95d;错误描述 在新建工程中只导入了QFramework&#xff0c;Build WebGL(空场景) 会看到如下错误&#xff1a; Exception: Non-Public Constructor() not found! in QFramework.Saf…

点云 3D 目标检测 - SECOND(Sensors 2018)

点云 3D 目标检测 - SECOND&#xff08;Sensors 2018&#xff09;摘要1. 引言2. 相关工作2.1 基于前视图和图像的方法2.2 基于鸟瞰图的方法2.3 基于3D的方法2.4 基于融合的方法3. SECOND检测器3.1 网络架构3.1.1 点云分组3.1.2 Voxelwise特征提取器3.1.3 稀疏卷积中间提取器3.1…

代码模板3---基础算法(双指针算法/位运算/离散化/区间合并)

①双指针算法 一般做题&#xff1a;先用暴力做法&#xff0c;然后去看是否存在某种性质&#xff08;如单调性&#xff0c;维护这个区间的单调性即可&#xff09; AcWing 799. 最长连续不重复子序列 - AcWing AcWing 800. 数组元素的目标和 - AcWing AcWing 2816. 判断子序列 - …

期货交易软件哪个好?为什么选择期货MT4平台软件?

越来越多人选择期货投资&#xff0c;因为期货投资具有高投资高回报的特征。在做期货交易时&#xff0c;很多投资者会发现市场上有各种各样的期货交易软件。如果第一次接触期货交易&#xff0c;难免会不知道选择哪个期货交易软件更好。下面为大家讲讲期货交易软件哪个好&#xf…

centos7安装rabbitmq集群

公司号口项目让运维安装rabbitmq&#xff0c;结果rabbitmq页面state都显示down了&#xff0c;问运维居然说都正常&#xff0c;麻蛋欺负我无知&#xff0c;自己搞一遍试试。 前言 RabbitMQ是一个开源的强大的企业消息系统&#xff0c;支持主流的操作系统&#xff0c;支持多种开…