<JavaEE> synchronized关键字和锁机制 -- 锁的特点、锁的使用、锁竞争和死锁、死锁的解决方法

news2025/1/23 22:38:26

目录

一、synchronized 关键字简介

二、synchronized 的特点 -- 互斥

三、synchronized 的特点 -- 可重入

四、synchronized 的使用示例

4.1 修饰代码块 - 锁任意实例

4.2 修饰代码块 - 锁当前实例

4.3 修饰普通方法 - 锁方法所在实例

4.4 修饰代码块 - 锁指定类对象

4.5 修饰静态方法 - 锁方法所在类对象

五、锁竞争和死锁

5.1 出现死锁的三种典型场景

5.1.1 “重复锁”

5.1.2 “互相锁”

5.1.3 “复杂锁”

5.2 死锁产生的必要条件

5.3 解决死锁的方案


一、synchronized 关键字简介

概述:Java中加锁的方式有很多种,其中使用 synchronized 关键字进行加锁是最常用的。synchronized 是一种监视器锁(monitor lock)。
加锁的目的:是为了将多个操作“打包”为一个有“原子性”的操作。
加锁的核心规则:进行加锁的时候必须先准备好“锁对象”,锁对象可以是任何类型的实例。
synchronized 的底层实现:synchronized 的底层是使用操作系统的 mutex lock 实现的,本质上依然是调用系统的 API ,依靠 CPU 的特定指令完成加锁功能的。

二、synchronized 的特点 -- 互斥

1)什么是互斥?
某个对象使用了 synchronized 进行修饰,当一个线程访问这个对象时,就会加锁,其他线程想要访问这个对象,就会先阻塞等待,直到这个对象解锁。这就是使用 synchronized 关键字时产生的互斥效果。
2)什么是加锁、解锁?

当程序进入由 synchronized 修饰的代码块、对象或方法时,即相当于加锁。

当程序退出由 synchronized 修饰的代码块、对象或方法时,即相当于加锁。

3)由互斥到冲突,什么是锁冲突/锁竞争?

由于 synchronized 具有互斥的特点,因此当多个线程同时竞争同一个锁时,线程间的冲突就不可避免。当有一个线程获得了锁,那么此时其他还想获得该锁的线程就只能阻塞等待,直到锁被释放后,才能再次竞争这个锁。这就是锁冲突或者说锁竞争。


三、synchronized 的特点 -- 可重入

1)什么是不可重入锁?

同一个线程在还没释放锁的情况下,访问同一个锁。

从 synchronized 的互斥特点可以了解到,当锁未被释放,访问该锁的线程会阻塞等待。

由于锁还没有释放,第二次加锁时,线程进入阻塞等待。

线程进入阻塞等待,则第一次的锁无法释放。

这样程序就进入了僵持状态。

这种状态被称为“死锁”。

而这样的锁,被称为“不可重入锁”。

2)什么是可重入锁?

可重入锁与不可重入锁不同,不会出现自己把自己锁死的情况。synchronized 就是可重入锁。

3)可重入锁是怎么实现可重入的?

可重入锁,锁内部会有两个属性,分别是“线程持有者”和“计数器”。

线程持有者

记录了当前锁是被哪一个线程持有的。

当发生重复加锁时,会判断是否是同一线程加锁。

如果是则跳过加锁步骤,只是在另一个属性“计数器”上自增1。

如果不是,则阻塞等待。

计数器

用于记录当前锁的加锁次数。

每次加锁,“计数器”计数会自增1(比如重复加锁10次,那么计数器的值就会等于10)。

每次解锁,“计数器”计数会自减1,当计数器的值归零时,才是真正的释放锁,此时该锁才能被其他线程获取。


四、synchronized 的使用示例

4.1 修饰代码块 - 锁任意实例

public class Test{
    //创建任意类型实例作为锁对象;
    Object locker = new Object();

    public void lockTest(){
        //使用synchronized,指定locker作为锁对象,在需要加锁的代码块上加锁;
        synchronized (locker) {
            //需要加锁的代码;
        }
    }
}

4.2 修饰代码块 - 锁当前实例

public class Test{
    public void lockTest(){
        //使用synchronized,指定this(当前实例)作为锁对象,在需要加锁的代码块上加锁;
        synchronized (this) {
            //需要加锁的代码;
        }
    }
}

4.3 修饰普通方法 - 锁方法所在实例

public class Test{
    //在普通方法上,使用synchronized,指定当前实例作为锁对象,将方法加锁;
    public synchronized void lockTest(){
        //需要加锁的代码;
    }
}

4.4 修饰代码块 - 锁指定类对象

//任意类;
public class Locker{

}

public class Test{
    public void lockTest(){
        //使用synchronized,指定class(类对象)作为锁对象,在需要加锁的代码块上加锁;
        synchronized (Locker.class) {
            //需要加锁的代码;
        }
    }
}

4.5 修饰静态方法 - 锁方法所在类对象

public class Test{
    //在静态方法上,使用synchronized,指定当前类对象作为锁对象,将方法加锁;
    public synchronized static void lockTest(){
        //需要加锁的代码;
    }
}

五、锁竞争和死锁

1)由锁竞争到死锁,什么是死锁?

上文在“synchronized 的特点 -- 互斥”中,介绍了什么是锁竞争。

人可以卷,但卷到一定程度就可以卷死自己或卷死别人。

那么,锁,也是可以卷的,比如锁竞争

加锁可以解决线程安全问题,但是如果加锁方式不当,就可能产生死锁。

2)死锁对程序来说意味着什么?

死锁是程序中最严重的一类BUG。

程序可能因此停摆、崩溃。

当然,人也可能因此“停摆、崩溃”。

5.1 出现死锁的三种典型场景

死锁有以下三种典型场景。
<1>

“重复锁”:如,一个线程,一把锁,自己把自己拷上了。

<2>“互相锁”:如,两个线程,两把锁,互相把对方拷上了。
<3>“复杂锁”:如,上述两种锁重复或复合发生的情况,多个线程多把锁,超级加倍。
以上三个锁的名字,是笔者归纳总结后,为方便记忆而概括出的锁名,不是公认的专业名词。

5.1.1 “重复锁”

“重复锁”是指什么情况?

锁在被释放前,同一个线程再次要求获得同一个锁。

锁没被释放,线程无法获得锁,进入阻塞。

但线程阻塞,代码就不会继续执行,锁也就一直得不到释放。

由此实现了自己卡死自己的“壮举”。

代码演示死锁:

    public static void main(String[] args) {
        //创建任意类型实例作为锁对象;
        Object locker = new Object();

        Thread t = new Thread(()->{
            //指定locker作为锁对象;
            synchronized (locker) {
                //再次指定locker作为锁对象;
                synchronized (locker){
                    //需要加锁的代码;
                }
            }
        });
    }
synchronized  是“可重入锁”。

“可重入锁”和“不可重入锁”的定义和区别,在上文“synchronized 的特点 -- 可重入”中说明了。

Java 提供的 synchronized 关键字,实现的是一个“可重入锁”。所以不用担心会发生这种死锁。

5.1.2 “互相锁”

1)“互相锁”是指什么情况?

两个线程,都获取了一个不同的锁。

但是在各自的锁释放前,又分别去获取了对方的锁。

但此时两把锁都还没有被释放,那么两个线程都进入阻塞等待的状态,都在等对方把锁释放。

代码演示死锁:

    public static void main2(String[] args) {
        //创建两个任意类型实例作为锁对象;
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(()->{
            //指定locker1作为锁对象;
            synchronized (locker1) {
                //指定locker2作为锁对象;
                synchronized (locker2) {
                    //需要加锁的代码;
                }
            }
        });
        Thread t2 = new Thread(()->{
            //指定locker2作为锁对象;
            synchronized (locker2) {
                //指定locker1作为锁对象;
                synchronized (locker1) {
                    //需要加锁的代码;
                }
            }
        });
    }

5.1.3 “复杂锁”

“复杂锁”是指什么情况?

“复杂锁”指前两种情况重复发生,或复合发生时,锁与锁之间相互叠加、“犬牙交错”的局面。

图示演示死锁:

5.2 死锁产生的必要条件

产生死锁有以下四个必要条件:
<1>互斥,获取锁的过程需要是互斥的,当锁被一个线程获取,另一个线程想获取这把锁就必须阻塞等待。这是锁的基本特性之一。
<2>不可劫取。锁被一个线程获取后,另一个线程不能强行把锁抢走,除非锁被持有线程释放。这也是锁的基本特性之一。
<3>请求保持。当一个线程申请锁而进入阻塞等待时,对自己已经持有的锁保持持有状态。这个条件与代码结构相关。
<4>循环等待/环路等待。线程申请锁,而锁在等待线程释放,形成环路。这个条件与代码结构相关。

5.3 解决死锁的方案

解决死锁的方案有以下几种方法:
<1>超时放弃。线程进入阻塞等待,当等待时间超过预设时间,则获取锁失败,将持有的锁释放。
<2>依序加锁。指定加锁的顺序规则,所有线程都需要按照规则规定的加锁顺序进行加锁。

图示演示依序加锁:


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

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

相关文章

threadlocal - 黑马程序员

目录 1、ThreadLocal介绍1.2 ThreadLocal基本使用1.2.1、常用方法1.2.2 使用案例 1.3 ThreadLocal类与synchronized关键字 2、运用场景_事务案例3、ThreadLocal的内部结构4、 ThreadLocal的核心方法源码5、ThreadLocalMap源码分析5.2 弱引用和内存泄漏 课程地址&#xff1a; ht…

【大学英语视听说上】“智力”口语问答练习

题目&#xff1a; book 2, page 9, question 4 回答&#xff1a; 1: What do you think of the view “Intelligence must be bred, not trained”? I think this view is biased. The view suggests that intelligence is primarily determined by genetic factors and inh…

drawio画图工具的四种使用方式

1、免安装使用&#xff08;绿色版&#xff09; 这种直接下载下来直接就可以使用&#xff0c;属于绿色版&#xff08;开箱即用&#xff09;&#xff0c;适用于个人 点击下载地址 2、 安装使用 这种下载下来就需要安装才可使用&#xff0c;适用于个人 点击下载地址 3、war包…

P3 Linux应用编程:系统调用与库函数

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f6f8;推荐专栏3: ​​​​​​《 链表_Chen…

轻量封装WebGPU渲染系统示例<42>- vsm阴影实现过程(源码)

前向实时渲染vsm阴影实现的主要步骤: 1. 编码深度数据&#xff0c;存到一个rtt中。 2. 纵向和横向执行遮挡信息blur filter sampling, 存到对应的rtt中。 3. 将上一步的结果(rtt)应用到可接收阴影的材质中。 具体代码情况文章最后附上的实现源码。 当前示例源码github地址: …

Azure Machine Learning - 使用 Python 进行语义排名

在 Azure AI 搜索中&#xff0c;语义排名是查询端功能&#xff0c;它使用 Microsoft AI 对搜索结果重新评分&#xff0c;将具有更多语义相关性的结果移动到列表顶部。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&am…

基于单片机的多功能视力保护器(论文+源码)

1.系统设计 多功能视力保护器在设计过程中能够对用户阅读过程中的各项数据信息进行控制&#xff0c;整体设计分为亮种模式&#xff0c;分别是自动模式&#xff0c;手动模式。在自动模式的控制下&#xff0c;当单片机检测当前光照不强且有人时就开启LED灯&#xff0c;并且会根据…

实现跨平台高手必修的课程,玩转Flutter动态化的解决的一些精华部分总结

Flutter作为一种快速、可靠的跨平台移动应用开发框架&#xff0c;在动态化方面也有很多令人兴奋的特性。本文将总结Flutter动态化的一些精华部分&#xff0c;帮助开发者更好地利用这些功能。 正文&#xff1a; 在实现跨平台高手必修的课程中&#xff0c;Flutter动态化是一个不…

kubernetes七层负载Ingress搭建(K8S1.23.5)

首先附上K8S版本及Ingress版本对照 Ingress介绍 NotePort&#xff1a;该方式的缺点是会占用很多集群机器的端口&#xff0c;当集群服务变多时&#xff0c;这个缺点就愈发的明显(srevice变多&#xff0c;需要的端口就需要多) LoadBalancer&#xff1a;该方式的缺点是每个servi…

人工智能学习5(特征抽取)

编译环境&#xff1a;PyCharm 文章目录 编译环境&#xff1a;PyCharm 特征抽取无监督特征抽取(之PCA)代码实现鸢尾花数据集无监督特征抽取 有监督特征抽取(之LDA)代码实现,生成自己的数据集并进行有监督特征抽取(LDA)生成自己的数据集PCA降维和LDA降维对比 代码实现LDA降维对鸢…

mysql服务日志打印,时区不对的问题

查资料发现 原来日志的时区和服务器的时区不是一个参数控制的 log_timestamps 单独控制日志的时区 show global variables like log_timestamps;看到默认的是UTC&#xff0c;只需要修改为和系统一致就行 #数据库中直接修改 set global log_timestampsSYSTEM;#配置文件my.cn…

NacosSync 用户手册

手册目标 理解 NacosSync 组件启动 NacosSync 服务通过一个简单的例子&#xff0c;演示如何将注册到 Zookeeper 的 Dubbo 客户端迁移到 Nacos。 介绍 NacosSync是一个支持多种注册中心的同步组件,基于Spring boot开发框架,数据层采用Spring Data JPA,遵循了标准的JPA访问规范…

面试题:MySQL为什么选择B+树作为索引结构

文章目录 前言二、平衡二叉树(AVL)&#xff1a;旋转耗时三、红黑树&#xff1a;树太高四、B树&#xff1a;为磁盘而生五、B树六、感受B树的威力七、总结 前言 在MySQL中&#xff0c;无论是Innodb还是MyIsam&#xff0c;都使用了B树作索引结构(这里不考虑hash等其他索引)。本文…

视频生成的发展史及其原理解析:从Gen2、Emu Video到PixelDance、SVD、Pika 1.0

前言 考虑到文生视频开始爆发&#xff0c;比如11月份就是文生视频最火爆的一个月 11月3日&#xff0c;Runway的Gen-2发布里程碑式更新&#xff0c;支持4K超逼真的清晰度作品(runway是Stable Diffusion最早版本的开发商&#xff0c;Stability AI则开发的SD后续版本)11月16日&a…

wordpress安装之Linux解压缩安装

本次教程是为了让大家少走弯路&#xff0c;可以更直观的去认识我们不懂的知识面。 首先我们安装解压缩的软件 命令如下&#xff1a; yum install -y unzip 上一篇我们讲到传输文件了 这篇我们把传输过来的压缩包解压并进行安装。follow me&#xff01; 我们输入命令 unzi…

Spring Cloud NetFlix

文章目录 Spring Cloud1 介绍2 Eureka&#xff08;服务注册与发现&#xff09;2.1 介绍2.2 服务注册与发现示例2.2.1 Eureka Server&#xff1a;springcloud-eureka2.2.2 Eureka Client&#xff1a;springcloud-provider2.2.3 Eureka Client&#xff1a;springcloud-consumer 2…

前端可滚动分类商品List列表 可用于电商分类列表

前端可滚动分类商品List列表 可用于电商分类列表 引言 在电商应用中&#xff0c;一个高效、用户友好的分类列表是提高用户体验和转化率的关键。本文将探讨如何使用xg-list-item、uni-grid和xg-tab等组件创建一个可滚动的分类商品列表&#xff0c;并处理相关的用户交互事件&…

Python容器——字典

Key——Value 键值对

canvas 轮廓路径提取效果

前言 微信公众号&#xff1a;前端不只是切图 轮廓 对内容做border效果&#xff0c;可以先看下代码运行的效果 内容是黑线构成的五角星&#xff0c;其轮廓就是红线的部分&#xff0c;本文主要介绍如何在canvas中实现这种效果 Marching Square 这里运用到的是marching square算法…

探索 Web API:SpeechSynthesis 与文本语言转换技术

一、引言 随着科技的不断发展&#xff0c;人机交互的方式也在不断演变。语音识别和合成技术在人工智能领域中具有重要地位&#xff0c;它们为残障人士和日常生活中的各种场景提供了便利。Web API 是 Web 应用程序接口的一种&#xff0c;允许开发者构建与浏览器和操作系统集成的…