锁策略和synchronized

news/2024/12/2 18:46:51/

1.常见的锁策略

(1)乐观锁 和 悲观锁

乐观锁:预测锁竞争的情况不激烈(工作量较少)

悲观锁:预测锁竞争的情况很激烈(工作量较多)

(2)轻量级锁 和 重量级锁

轻量级锁:加锁和解锁的开销较小,效率更高。(乐观锁通常是一个轻量级锁)

重量级锁:加锁和解锁的开销较大,效率较低。(悲观锁通常是一个重量级锁)

(3)自旋锁 和 挂起等待锁

自旋锁:是一种典型的轻量级锁,会不断去尝试获取锁。

挂起等待锁:是一种典型的重量级锁,会进入阻塞队列,暂时不参与cpu调度。

(4)互斥锁 和 读写锁

互斥锁:synchronized锁就是一个互斥锁,如果一个线程加锁了,另一个线程要获取锁,会进入阻塞等待。

读写锁:如果多个线程都只进行读操作,那么此时是没有线程问题的,无需加锁。而如果在锁中的代码有读也有写,或者全是写(简单来说就是代码中带有写操作)才会进行加锁。

(5)公平锁 和 非公平锁

公平锁:假如有两个线程一个阻塞等待了1分钟,一个等待10秒钟,此时锁被释放会先给等待时间长的加锁。

非公平锁:仍然是上面的情况,但是此时它们的获取概率相等。

(6)可重入锁 和 不可重入锁

可重入锁:一个线程重复加多次锁,不会死锁。

不可重入锁:一个线程重复加两次锁,会出现死锁。

2.synchronized实现了哪些锁策略

(1)synchronized既是一个乐观锁又是一个悲观锁

默认是乐观锁,如果竞争激烈会转变为悲观锁。

(2)synchronized既是一个轻量级锁又是一个重量级锁

默认是轻量级锁(乐观锁),如果竞争激烈会转变为重量级锁(悲观锁)。

(3)synchronized的轻量级锁是基于自旋锁的方式实现的;synchronized的重量级锁是基于挂起等待锁的方式实现的。

(4)synchronized是互斥锁,不是读写锁

(5)synchronized是非公平锁

(6)synchronized是可重入锁

3.synchronized原理

synchronized的工作原理是当两个线程针对同一个对象加锁时,会产生阻塞等待。

除此之外,synchronized的内部还有一些优化机制,目的是为了让这个锁更加高效好用。

3.1 锁升级/锁膨胀

synchronized在加锁后会进入这样的几种状态。

(1)无锁

此时synchronized没有被加锁

(2)偏向锁

此时有线程对其加锁,但是此时的加锁并非真正的加锁操作,只是在锁对象上打了一个标记,并没有进行实际的加锁操作,此时的偏向锁比起真正的加锁的开销要少了很多,但是这种状态只存在与没有锁竞争的情况下,如果有其他线程对其尝试加锁,那么就会进行真正的加锁操作,反之,如果执行到了末尾也没有其他线程来加锁,此时就不会加锁,直接把标记消除掉就好了。

(3)轻量级锁

当在偏向锁的阶段有其他线程对其进行加锁,也就是发生锁竞争的时候,那么偏向锁就会升级成为轻量级锁,此时会通过自旋锁的方式进行加锁的,但是不断的去获取锁也会占用cpu资源。此时,如果加锁的线程很快就将锁释放掉,那么自然相安无事,此时的自旋也是划算的;但是,如果迟迟拿不到锁,也就是自旋到一定程度时,就会升级成为重量级锁(挂起等待锁)。

(4)挂起等待锁

如果线程进行了重量级锁的加锁,并且发生了锁竞争,此时竞争的线程就会放入到阻塞队列中,暂时不参与cpu的调度,直到锁被释放,这个线程才有机会被调度到,并且有机会获取锁。

注意:锁的升级是不可以倒退的。

3.2 锁消除

锁消除是编译器的一种智能判定,判断当前代码是否真的需要加锁。

如果加锁代码在某个场景中没有线程安全问题,不需要加锁,那么编译器就会把锁给干掉。

比如StringBuffer,它的关键方法都带有synchronized,但是如果我在单线程的环境下去使用它,那么它的加锁操作就会变得毫无意义,此时编译器就会将锁给干掉了。

3.3 锁粗化

通常情况下,我们希望锁的粒度越细越好,也就是加锁的代码越少越好,因为锁的粒度越细,可以被并发的代码就越多,反之就越少。

但是在有些情况下,锁的粒度反而粗一点会更好,如下:

图中被红色圈住的部分是要加锁的代码,没被圈住的部分是可以不加锁的代码,此时可以看到,每次的解锁与加锁之间的间隔非常的小,因为加锁和解锁的操作也是有一定开销的,在一段代码中频繁大量的进行加锁解锁操作,那么并发带来的效益可能还不如频繁加锁的带来消耗多。就好比:

我完成了三个工作,要汇报给自己的领导

打电话--汇报工作1--挂电话 间隔一分钟

打电话--汇报工作2--挂电话 间隔一分钟

打电话--汇报工作3--挂电话

此时领导一定被你烦的不行,为什么不可以一次性汇报完呢?

在这里也是同样的道理,打电话相当于加锁,挂电话相当于解锁,如果将那些小的间隔也一并加入锁中,如下:

此时,将多个加锁操作合并为一个整体,就可以将上述的情况解决了。


http://www.ppmy.cn/news/11891.html

相关文章

【每日一题】【LeetCode】【第十二天】区域和检索 - 数组不可变

解决之路 题目描述 测试案例(部分) 第一次 emmm,说实话,一开始我还真没看懂题目是什么意思。。。。 自己按我自己理解的方式写了一下代码,用测试案例跑了下,成功了。 不过,放进去跑不通&…

【Linux】六、Linux 基础IO(一)|重谈文件|C语言文件操作|操作系统文件操作(系统文件I/O)|文件描述符

目录 一、重谈文件 二、C语言文件操作 2.1 重谈C语言文件操作 2.2 补充细节 三、操作系统文件操作(系统文件I/O) 3.1 文件相关系统调用:close 3.2 文件相关系统调用:open 3.2.1 open 的第二个参数 flags 3.2.2 open 的第…

自定义类型:结构体,枚举,联合(2)

TIPS 1. 类型的定义可以考虑放在头文件里头。 2. 一个汉字存储的时候占两个字节空间 3. 关于结构体变量初始化的一些细节 4. 关于结构体内存对齐的补充 1. 2. S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。 3. 这两个结构体类型成员都…

C++ Socket 构造函数参数解析

int socket(int af, int type, int protocol); 1、) af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地…

Android ANR bugreport log分析

最近工作中频繁遇到设备ANR问题,而且是概率性的那种,于是决定花点时间找找规律复现分析下 说道这里,抓日志是问题解决的最有效途径,这里不得不说一下 bugreport log,其实网上关于它的分析方法有很多,在此仅仅是为了记录…

自定义类型:结构体,枚举,联合(3)

TIPS 1. 2. 枚举 1. 枚举顾名思义就是一一列举可能的取值,比如一周的星期一到星球天是有限的七天,可以一一列举。有比如性别,月份。 2. 像这种容易并且可以被一一列举的数据我们就可以定义为枚举类型。 枚举类型 1. 枚举的关键字为e…

【Git】GitHub 操作

6、GitHub 操作 GitHub 网址:https://github.com/ Ps:全球最大同性交友网站,技术宅男的天堂,新世界的大门,你还在等什么? 账号姓名验证邮箱atguiguyueyue岳不群atguiguyueyuealiyun.comatguigulinghuchong令狐冲atguigulinghu…

JavaScript 数据处理 · 基本统计(文末附视频)

第 5 节 基本数据处理 基本统计 学习了如何对 JavaScript 中的数组数据进行操作之后,我们就要回到刚开始选择购买这本小册的目的了:使用 JavaScript 开发灵活的数据应用。既然说是数据应用,那么便离不开统计计算,而数组就可以说…