synchronized关键字-监视器锁(monitor lock)

这就是我们上一篇中代码提到的加锁的主要方式,本质上是调用系统api进行加锁,系统api本质是靠cpu特定指令加锁.

synchronize的特性

互斥性

synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,,其它线程如果也执行到同一个对象synchronized就会阻塞等待(锁冲突/锁竞争)

进入synchronized修饰的代码块,相当于加锁.

退出synchronized修饰的代码块,相当于解锁.

让我们回顾一下上一篇中这一段代码:

synchronized (locker) {//locker是锁对象,后面会讲count++;
}

进入代码块内部(第一个大括号),相当于针对当前对象加锁.

执行完毕(出第二个大括号)相当于针对当前对象"解锁" .

让我们在多线程的场景下分析一下这个过程.

通过锁竞争可以让第二个线程指令无法插入到第一个线程指令中间,但此时第一个线程仍可被调度cpu. 

上述过程就可以看作不同的人(线程)排队上厕所,一个人进去就得上锁,这时其它人进不去,直到那个人开了锁才可以.

理解"阻塞等待"

针对每一把锁,操作系统内部都维护了一个等待队列.当这个锁被某个线程占用的时候,其它线程尝试进行加锁,就加不上了,就会阻塞等待,一直等到之前的线程解锁之后,由操作系统唤醒一个新的线程,再来获取到这个锁.

注意:

上一个线程解锁之后,下一个线程并不是就能够立即获取到锁.而是要靠操作系统来"唤醒".这也就是操作系统线程调度的一部分工作.

假设有A B C三个线程,线程A先获取到锁,然后B尝试获得锁,然后C尝试获得锁,此时B和C都在阻塞队列中排队等待.但是A释放锁之后,虽然B比C先来的,但是B不一定能获取到锁,而是和C重新竞争,并不遵守先来后到的规则.

利用锁确实对多线程执行效率有影响,但这样仍会比串行执行快,因为锁以外的的内容仍然是并发执行的

可重入

定义:synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

因此Java不会出现锁死问题,但锁死的内容仍需要了解

理解"把自己锁死"

一个线程没有释放锁,然后又尝试加锁.

//第一次加锁,加锁成功

lock();

//第二次加锁,锁已经被占用,阻塞等待.

lock();

按照之前锁的设定,第二次加锁的时候,就会阻塞等待.直到第一次的锁被释放,才能获取到第二个锁.但是释放第一个锁也是由该线程来完成,结果这个线程已经躺平了,啥都不想干了,就无法进行解锁操作.这时候就会死锁.

死锁的三种典型场景

1.一个线程一把锁:如果锁不是可重入锁.并且一个线程对这把锁2次就会出现死锁(把钥匙锁在屋里了).

public class TestLock {public static Object locker = new Object();public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}}}});Thread t2 = new Thread(() -> {for(int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

这里的t1按理来说是死锁的类型,不过synchronized是可重入锁,所以可以正常执行.

2.两个线程两把锁:线程1获取到锁A,线程2获取到锁B,接下来线程1尝试获取到锁B,线程2尝试获取到锁A就会导致死锁.(房子的钥匙锁车里了,车钥匙锁房子里了). 

public class TestLock2 {public static Object A = new Object(), B = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {synchronized (A) {//sleep一下,是给t2时间,让t2也能拿到Btry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试获取B,并没有释放Asynchronized (B) {System.out.println("t1拿到了两把锁");}}});Thread t2 = new Thread(() -> {synchronized (B) {//sleep一下,是给t1时间,让t1能拿到Atry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试获取A,并没有释放Bsynchronized (A) {System.out.println("t2 拿到了两把锁");}}});t1.start();t2.start();}
}

因此,形如这样的代码不会执行到第二次获取锁内的内容,通过jconsole可以观察到原因.

 

可见,两个线程都卡在了获取对方已获取得到 的锁的地方,而且状态为BLOCKED.

不过在这种情况下,仍可以通过约定加锁顺序来解决问题.

3.n个线程可以获取到m把锁. (建议看一下哲学家吃饭问题,这里就不过多讲解了).

死锁的四个必要条件(以下的条件缺一不可,缺一个不构成死锁)

1.互斥使用:获取锁的过程是互斥的.一个线程拿到了这把锁,另一个线程也想获取,就需要阻塞等待.(锁的基本特性,不好破坏)

2.不可抢占:一个线程拿到锁之后,只能主动解锁,不能让别叠对象把锁强行抢走(锁的基本特性,也不好破坏)

3.请求保持:一个线程拿到锁A之后,在持有A的条件下,尝试获取B(代码结构,看实际需求)

4.循环等待(环路等待):一个想获取到另一个的锁,另一个又在等其它的.(是最容易破坏的:指定一定规则,可避免循环等待->比如指定加锁顺序)

解决死锁的方案

(1)引入一个额外的锁

(2)去掉一个线程

(3)引入计数器,限制同时工作的线程数

(4)前面三个方案普适性不高,还是建议这个:引入加锁规则

以下面的代码为例,让我们分析一下synchronized的可重入性.

 在可重入锁的内部,包含着"线程持有者"和"计数器"两个信息.

如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己,那么就可以继续获取到锁,让计数器自增.(真正加锁,同时给计数器+1(初始为0,加锁之后变成1了,说明当前这个对象被该线程加锁一次),同时记录线程是谁,解锁时把count--,直到count减到零,才算是真正的解锁了)

synchronized使用实例

synchronized本质上要修改指定对象的"对象头".从使用角度来看,synchronized也一定要搭配一个具体的对象使用.

修饰代码块

明确指定锁哪个对象(比较常用的方法).

锁任意对象:

public class TestSynchronizedDemo {private Object locker = new Object();public void method() {Synchronized (locker) {}}
}

锁当前对象:

public class TestSynchronizedDemo {public void method() {synchronized(this) {//需要理解好这里是不是同一个对象}}
}

直接修饰普通方法

锁的TestSynchronizedDemo对象

public class TestSynchronizedDemo {public synchronized void method() {//相当于给this加锁(锁对象this)}
}

 修饰静态方法(不常见)

锁的TestSynchronizedDemo类对象(就如果synchronized是加到static方法上,相当于给类加锁).

public class TestSynchronized {public synchronized static void method() {}
}

 我们要重点理解,synchronized锁的是什么.两个线程竞争同一把锁,才会产生阻塞等待

两个线程分别尝试获取两把不同的锁,不会产生锁竞争.

Java标准库中的线程安全类

Java标准库中很多线程都是不安全的.这些类可能涉及多线程修改共享数据,也没有任何加锁措施

比如:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder

但还是有一些是线程安全的.使用了一些锁机制来控制.

Vector(不推荐使用),HashTable(不推荐使用),ConcurrentHashMap,StringBuffer(不推荐使用)

还有的是没有加锁,但因为不涉及修改的特殊类,也是线程安全的.

String

 

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

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

相关文章

Python实现FA萤火虫优化算法优化卷积神经网络回归模型(CNN回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , …

小航助学题库白名单竞赛考级蓝桥杯等考scratch(8级)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09; 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;

Java核心知识点整理大全26-笔记

目录 27. Storm 7.1.1. 概念 27.1.1. 集群架构 27.1.1.1. Nimbus&#xff08;master-代码分发给 Supervisor&#xff09; 27.1.1.2. Supervisor&#xff08;slave-管理 Worker 进程的启动和终止&#xff09; 27.1.1.3. Worker&#xff08;具体处理组件逻辑的进程&#xff…

分包(微信小程序)

首先&#xff0c;微信小程序中使用分包是为了减少首屏的请求&#xff0c;因为微信小程序会默认下载主包内的内容并展示到页面上&#xff0c;但是随着业务量的增加&#xff0c;代码量也会越来越大。会导致我们启动小程序的时候首页加载速度过慢的这个问题。这时我们就可以采用分…

解决(error) ERR Errors trying to SHUTDOWN. Check logs.问题~

该问题出现在我在使用shutdown关闭redis服务器时&#xff0c;出现该问题的原因是由于配置文件的日志文件位置未配置或者缺少日志文件 我自己出现该问题是因为缺少日志文件&#xff0c;解决步骤如下所示&#xff1a; 第一步&#xff1a;在该目录下使用touch命令创建日志文件 第…

【工具与中间件】快速构建飞书群聊机器人

文章目录 0. 前言1. 无痛入门1.1 飞书创建群聊机器人1.2 Webhook 请求示例1.3 设置安全策略 2. 实战3. 总结3.1 使用体验3.2 深入使用建议 先进团队用飞书&#xff0c;先进飞书群聊有...... 0. 前言 科技蓬勃发展的今天&#xff0c;我们可以轻松拥有属于自己/团队的机器人。日…

Ubuntu18.04 本地安装CVAT标注工具

写在前面&#xff1a; 1、如果直接clone最新版本的cvat&#xff0c;python版本最好安装3.8的&#xff0c;因为其中部分代码的语法只有高版本的python才可以支持。 2、安装完成以后本地登陆可能出现"cannot connect to cvat server"的错误&#xff0c;可以从Cannot …

制作麒麟oem镜像之通过识别终端品牌与BIOS安装不同内容

原文链接&#xff1a;麒麟oem镜像之识别终端品牌与BIOS安装不同内容 **hello&#xff0c;大家好啊&#xff01;**在个性化和定制化日益成为趋势的今天&#xff0c;操作系统的定制化安装也变得越来越重要。特别是对于企业用户来说&#xff0c;能够根据不同的硬件配置和需求&…

项目实战之RabbitMQ重试机制进行消息补偿通知

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 文章目录 &#x1f31f;架构图&#x…

Python Tkinter库入门与基础

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Tkinter是Python标准库中内置的图形用户界面&#xff08;GUI&#xff09;工具包&#xff0c;提供了创建窗口、按钮、文本框等GUI元素的功能。本文将介绍Tkinter的基础知识&#xff0c;帮助大家快速入门。 安装与…

低成本物联网安全芯片ACL16_S 系列,可应用物联网认证、 SIM、防抄板和设备认证等产品上

ACL16_S 芯片是针对物联网认证、 SIM、防抄板和设备认证需求推出的高安全芯片。芯片采用 32 位 ARMCortex™-M0 系列内核&#xff0c;片内集成多种安全密码模块&#xff0c;包括 RSA/ECC DES/TDES、 SHA-1/-256、 AES-128/-192/-256 等国际安全算法&#xff0c;支持真随机数发…

岳阳楼3D模型纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 岳阳楼&#xff0c;位于湖南省岳阳市岳阳楼区洞庭北路&#xff0c;地…

Vcenter 6.7 VCSA证书过期问题处理

1. 故障现象 2022年10月25日&#xff0c;登陆VC报错。 按照报错信息&#xff0c;结合官方文档&#xff0c;判断为STS证书过期导致。 vCenter Server Appliance (VCSA) 6.5.x, 6.7.x or vCenter Server 7.0.x 在/var/log/vmware/vpxd-svcs/vpxd-svcs.log看到类似报错: ERRO…

为什么程序员会讨厌PHP编程语言?

闲来无事&#xff0c;逛了某乎看见一篇关于PHP编程的学习指南&#xff0c;深受启发。我们都知道PHP是一种流行的爬虫语言&#xff0c;尤其在Web开发方面。太有很多有点&#xff0c;例如简单易学、支持的成许多等。但是PHP也会存在一些缺点&#xff0c;如代码可读性差&#xff0…

Windows下Linkis1.5DSS1.1.2本地调试

1 Linkis: 参考&#xff1a; 单机部署 | Apache Linkis技术分享 | 在本地开发调试Linkis的源码 (qq.com)DataSphere Studio1.0本地调试开发指南 - 掘金 (juejin.cn) 1.1 后端编译 参考【后端编译 | Apache Linkis】】 修改linkis模块下pom.xml,将mysql.connetor.scope修改…

go-fastfds部署心得

我是windows系统安装 Docker Desktop部署 docker run --name go-fastdfs&#xff08;任意的一个名称&#xff09; --privilegedtrue -t -p 3666:8080 -v /data/fasttdfs_data:/data -e GO_FASTDFS_DIR/data sjqzhang/go-fastdfs:lastest docker run&#xff1a;该命令用于运…

Matlab 加权均值质心计算(WMN)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 思路很简单,即将之前的均值中心,引入相关的权重函数(通常与距离有关),以此为每个点进行赋权,最后即可得到一个加权均值中心: 二、实现代码 %% ********<

【五分钟】学会利用cv2.resize()函数实现图像缩放

引言 在numpy知识库&#xff1a;深入理解numpy.resize函数和数组的resize方法中&#xff0c;小编较为详细地探讨了numpy的resize函数背后的机理。从结果来看&#xff0c;numpy.resize函数并不适合对图像进行缩放操作。而opencv中的resize函数虽然和numpy的resize函数同名&…

【MATLAB源码-第95期】基于matlab的协作通信中(AF模式)中继选择算法对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 最大最小中继选择 (Max-Min Relay Selection)&#xff1a;这种算法选择能够提供最大最小信号强度的中继。它首先计算所有可用中继的信号强度&#xff0c;然后选择那些在最差信道条件下仍能保持最高信号强度的中继。其目的…

BACnet I/O模块在水利环境监测全自动控制系统中的应用:稳定、高效、实时

前言 “绿水青山就是金山银山”&#xff0c;水利环境一直是国际生态部门关注的重点。随着经济的发展、针对水利环境的监管也日趋严格&#xff0c;尤其是重点河、湖水系、水源地、城市内河等成为重点监管对象&#xff0c;监管力度也愈来愈严格&#xff0c;监测布点密度不断加大。…