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

目录

一、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 main(String[] args) {//创建两个任意类型实例作为锁对象;Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{//指定locker1作为锁对象;synchronized (locker1) {System.out.println("t1获取locker1");//休眠1秒,保证线程t2可以获取到锁locker2。try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//指定locker2作为锁对象;synchronized (locker2) {//需要加锁的代码;System.out.println("t1获取locker2");}}});Thread t2 = new Thread(()->{//指定locker2作为锁对象;synchronized (locker2) {System.out.println("t2获取locker2");//休眠1秒,保证线程t1可以获取到锁locker1。try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//指定locker1作为锁对象;synchronized (locker1) {//需要加锁的代码;System.out.println("t2获取locker1");}}});t1.start();t2.start();}//运行结果:
t2获取locker2
t1获取locker1
...程序没有正常执行完毕,因为出现了死锁。

5.1.3 “复杂锁”

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

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

图示演示死锁:

5.2 死锁产生的必要条件

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

5.3 解决死锁的方案

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

图示演示依序加锁:


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

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

相关文章

【从零开始学习JVM | 第二篇】字节码文件的组成

前言&#xff1a; 字节码作为JAVA跨平台的主要原因&#xff0c;熟练的掌握JAVA字节码文件的组成可以帮助我们解决项目的各种问题&#xff0c;并且在面试中&#xff0c;关于字节码部分的内容却是一大考点和难点&#xff0c;因此我们在这里穿插讲解一下字节码文件的组成。 目录 …

16、观察者模式(Observer Pattern)

观察者&#xff08;Observer Pattern&#xff09; 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。主要解决一个对象状态改变给其他对象通知的问题&#xff0c;而且要考虑到易用和低耦合&…

你好!哈希表【JAVA】

1.初识&#x1f3b6;&#x1f3b6;&#x1f3b6; 它基本上是由一个数组和一个哈希函数组成的。哈希函数将每个键映射到数组的特定索引位置&#xff0c;这个位置被称为哈希码。当我们需要查找一个键时&#xff0c;哈希函数会计算其哈希码并立即返回结果&#xff0c;因此我们可以…

【OpenGauss源码学习 —— (RowToVec)算子】

VecToRow 算子 概述ExecInitRowToVec 函数ExecRowToVec 函数VectorizeOneTuple 函数 ExecEndRowToVec 函数总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在…

github首次将文件合到远端分支,发现名字不是master,而是main

暂存区和本地仓库的信息都存储在.git目录中其中 其中&#xff0c;暂存区和本地仓库的信息都存储在.git目录中 在自己的github上实践 1、刚开始&#xff0c;git clone gitgithub.com:lingze8678/my_github.git到本地 2、在克隆后的代码中加入一个pdf文件 3、在git bash中操作…

CentOS增加虚拟内存 (Linux增加内存)

前言 因为囊中羞涩不敢言&#xff0c;所以内存只有2G&#xff0c;项目在运行的时候&#xff0c;占用的内存已经报表&#xff0c;所以有的时候就会出现宕机的情况发生&#xff0c;后面发现可以通过使用增加虚拟内存空间&#xff0c;来增加内存容量。 下面进入正题&#xff0c;讲…

Selenium+Python自动化测试之验证码处理

两种方式&#xff1a; 验证码识别技术 (很难达到100%) 添加Cookie &#xff08;*****五星推荐&#xff09; 方式一&#xff1a;验证码识别技术 逻辑方式&#xff1a; 1&#xff1a;打开验证码所在页面&#xff0c;截图。获取验证码元素坐标&#xff0c;剪切出验证码图片&…

【MATLAB】辛几何模态分解分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 辛几何模态分解&#xff08;CEEMDAN&#xff09;是一种处理非线性和非平稳信号的适应性信号分解方法。通过在信号中加入白噪声&#xff0c;并多次进行经验模态分解&#xff08;EMD&#…

深度学习TensorFlow2基础知识学习前半部分

目录 测试TensorFlow是否支持GPU&#xff1a; 自动求导&#xff1a; 数据预处理 之 统一数组维度 定义变量和常量 训练模型的时候设备变量的设置 生成随机数据 交叉熵损失CE和均方误差函数MSE 全连接Dense层 维度变换reshape 增加或减小维度 数组合并 广播机制&#…

clickhouse的向量化执行

背景 clickhouse快的很大一部分原因来源于数据的向量化执行&#xff0c;本文就来看一下向量化执行和正常标量执行的区别 SIMD的向量化执行 从上图可知&#xff0c;clickhouse通过SIMD指令可以做到一个cpu周期操作两个向量的运算操作&#xff0c;比起普通的cpu指令效率提高了N…

Understanding Computer Hardware

文章目录 I. Input Devices1. Keyboard&#xff08;1&#xff09;Layout&#xff08;2&#xff09;Key Types&#xff08;3&#xff09;Functionality&#xff08;4&#xff09;Connectivity&#xff08;5&#xff09;Ergonomics&#xff08;6&#xff09;Multimedia Keys&…

【计算机组成体系结构】主存储器的基本组成

一、半导体元器件存储二进制0/1的原理 一个存储器逻辑上分为MAR&#xff0c;MDR和存储体&#xff0c;这三块在时序逻辑电路的控制下相互配合工作。 而存储体有多个存储单元构成&#xff0c;每个存储单元又由每个存储元构成。一个存储元可以存放一位的二进制的0/1。 一个存储元…

OWASP安全练习靶场juice shop-更新中

Juice Shop是用Node.js&#xff0c;Express和Angular编写的。这是第一个 完全用 JavaScript 编写的应用程序&#xff0c;列在 OWASP VWA 目录中。 该应用程序包含大量不同的黑客挑战 用户应该利用底层的困难 漏洞。黑客攻击进度在记分板上跟踪。 找到这个记分牌实际上是&#…

想考研到电子类,未来从事芯片设计,目前该怎么准备?

最近看不少天坑学子想考研微电子专业&#xff0c;但却不知道该怎么准备&#xff1f;接下来就带大家一起来具体了解一下~ 首先是目标院校的选择&#xff1f; 目前所设的微电子专业学校里&#xff0c;比较厉害的有北京大学、清华大学、中国科学院大学、复旦大学、上海交通大学、…

ROS2教程08 ROS2的功能包、依赖管理、工作空间配置与编译

ROS2的功能包、依赖管理、工作空间配置与编译 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyrigh…

品牌是如何通过软文推广产品的?媒介盒子为您揭秘

需求是概念的、抽象的&#xff0c;产品是具象的&#xff0c;多维的。软文推广就是通过发现消费者的需求来促使消费者主动购买产品&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;品牌是如何通过软文推广产品的。 一、 差异化内容打出独特点 差异化内容指通过和竞品的分析…

基于Intel Ai Analytics Toolkit 及边缘计算的溶氧预测水产养殖监测方案

基于AI的淡水养殖水质溯源、优化系统方案 前言一、关键需求及方案概述二、方案设计预测机制LSTM 模型基于intel AI 的时序水质分析模型与分类模型优化 三、实战分析1、方案简述2、数据分析预处理特征类型处理特征分布分析 3、特征构造4、特征选择过滤法重要性排序 5.构建LSTM模…

算法--最短路

这里写目录标题 xmind单源最短路简介所有边权都是正朴素的Dijkstra算法思想例子题解 堆优化版的Dijkstra算法 存在负数权Bellman-Ford算法思想例子题解 多源汇最短路简介 xmind 上述中&#xff0c;朴素Dijkstra算法适用于稠密图 其他用堆优化版 而SPFA算法一般都比Bellman-For…

设计模式:装饰者模式

目录 一、定义 二、场景 三、例子 四、优缺点 优点&#xff1a; 缺点&#xff1a; 一、定义 在不改变已有对象结构的情况下&#xff0c;动态添加新的功能到对象上&#xff0c;是继承的一种替代方案。属于结构型模式。 二、场景 1.扩展一个类的功能&#xff0c;添加附加职责…

七、ZooKeeper选举机制

目录 1、概念 2、全新集群选举 3、非全新集群选举 zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜出