锁策略及优化总结
定期复盘总结,不断优化交易策略。 #生活技巧# #理财投资建议# #炒汇技巧#
锁策略
锁在加锁/解锁/遇到锁冲突的时候,都会怎么做。
常见锁策略:
悲观锁vs乐观锁:加锁的时候,预测当前锁冲突的概率是大还是小:
预测当前锁冲突概率大,后续要做的工作往往就会更多.加锁开销就更大(时间,系统资源)=>悲观锁.预测当前锁冲突概率不大,后续要做的工作往往就更少.加锁开销就更小(时间,系统资源)=>乐观锁.
重量级锁vs轻量级锁:一般来说,悲观锁往往就是重量级锁-》加锁过程做的事情多,重量
一般来说,乐观锁往往就是轻量级锁-》加锁过程做的事情少,轻量.
实际交流过程中,这两组概念可能会混着用,悲观乐观,是先预测一下, 锁冲突概率高还是低,又因为,一般锁冲突概率高的时候就得多做处理.
自旋锁vs挂起等待锁:自旋锁,是轻量级锁的一种典型实现方式:
void lock(){
while (true) {
if (锁是否被占用) {
continue;
}
获取到锁
break;
}
}
cpu在空转,忙等,消耗了更多的cpu资源,但是一旦锁被释放,就能第一时间拿到锁,拿到锁的速度更快,消耗cpu。
挂起等待锁,是重最级锁的一种典型实现方式:
借助系统中的线程调度机制,当尝试加锁,并且锁被占用了,出现锁冲突,就会让当前这个尝试加锁的线程被挂起. (阻塞状态),此时这个线程就不会参与调度了,直到这个锁被释放,然后系统才能唤醒这个线程,去尝试重新获取锁.
消耗的时间更长,一旦线程被阻塞 了,啥时候被唤醒,这个过程是不可控的,可能会经历很长很长的时间,拿到锁的速度更慢,节省cpu。
synchronized轻量级锁部分,基于自旋锁实现;重量级锁部分,基于挂起等待锁实现。
可重入锁Vs不可重入锁:synchronized就是可重入锁,一个线程针对这把锁,连续加锁两次不会死锁.
比如C++的std:mutex就是不可重入锁一个线程针对这把锁连续加锁两次,会死锁.
公平锁Vs非公平锁:公平锁:严格按照先来后到的顺序来获取锁,哪个线程等待的时间长,哪个线程就拿到锁.
非公平锁:若干个线程,各凭本事,随机的获取到锁,和线程等待时间无关了。
synchronized属于非公平锁,多个线程,尝试获取这个锁,此时是按照概率均等的方式来进行获取的。
互斥锁vs读写锁:synchronized本身就是普通的互斥锁 :加锁 和解锁
读写锁,则是-一个更特殊的锁:加读锁 加写锁, 解锁:
java的读写锁是这样设定的:
1)读锁和读锁之间,不会产生互斥
2)写锁和写锁之间,会产生互斥
3)读锁和写锁之间,会产生互斥
突出体现的是"读操作和读操作"之间是共享的(不会互斥的),有利于降低锁冲突的概率,提高并发能力。
synchronized优化
synchronized实现原理:
synchronized即使悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁.轻量级锁是自旋锁实现,重量级锁是挂起等待锁实现,是可重入锁不是读写锁,是非公平锁.
synchronized的优化策略
1、synchronized的"自适应":锁升级的过程:
1)未加锁的状态(无锁)
代码中开始调用执行synchronized,升级
2)偏向锁
遇到锁冲突,升级
3)轻量级锁
冲突进一步提升,升级
4)重量级锁
首次使用synchronized 对对象进行加锁的时候,不是真的加锁.而只是做一个“标记”(非常轻量非常快,几乎没有开销)
如果没有别的线程尝试对这个对象加锁,就可以保持这个状态,一直到解锁. (解锁也就是修改一下上述标记,也几乎没有开销)
但是如果在偏向锁状态下,有某个线程也尝试来对这个对象加锁,立马把偏向锁,升级成轻量级锁(真的加锁,真的有互斥了),也可以保证锁能够正常生效。
本质上,偏向锁策略就是“懒"字具体体现.能晚加锁,就晚加锁.
上述的升级过程,针对一-个锁对象来说,是不可逆的,- -旦升级到了重量级锁,不会回退到轻量级锁. (当前JVM里面的做法)
2、锁消除代码里写了加锁操作,编译器&JVM会对你当前的代码做出判定,看这个地方到底是不是真的需要加锁,如果这里不需要加锁,就会自动的把加锁操作,给优化掉,最典型的,就只在-个线程里, 使用synchronized,由于编译器优化,需要保证优化后的逻辑和优化前是要等价的,这里做的是比较保守的.能够起到的作用有限的,这个事情和前面谈到的°偏向锁"互不相干,也不冲突的.
3、锁粗化加锁的范围内,包含多少代码,包含的代码越多,就认为锁的粒度就越粗
有些逻辑中,需要频繁加锁解锁.编译器就会自动的把多次细粒度的锁,合并成- -次粗粒度的锁.
cas
compare and swap比较和交换:这是一条cpu指令,就可以完成比较和交换这样的一套操作下来.
cas的流程(想象成一个方法):
boolean cas(address, reg1, reg2) {
if (*address == reg1){
把address内存地址的值和reg2寄存器的值进行交换
return true;
}
return false;
}
*address=>获取内存地址
这里说是"交换",实际上更多的是用来"赋值",一般更关心内存中, 交换后的数据,而不关心reg2寄存器里交换后的数据,此处也可以近似的认为,上述操作就是把reg2的值赋值给内存中。
由于CPU提供了.上述指令,因此操作系统内核,也就能够完成上述操作,就会提供出这样的CAS的api
JVM又对于系统的CAS api进-步的封装了,在java代码中也就可以使用CAS操作了.
ABA问题:
使用CAS编写代码比较然后再交换,
在比较时,检查当前内存的值,是否被其他线程修改了,如果被修改了,就要稍后再重试,如果没被修改,接下来就可以直接修改. (不会有线程安全问题的,没有其他线程穿插执行)
值没变!=值没变过
可能另一个线程把这个值从A-> B,又从B-> A,ABA在大部分情况下,没啥事,但是在披端情况下,就可能会产生bug。
ABA问题的典型Bug场景:具体操作如下图:
若t1还没执行到 if 时,有一个人给账户转了500,产生线程t3,具体操作:
上述就是属于ABA问题的典型bug场景,属于非常极端的情况
如何避免ABA问题核心思路是,引入“版本号”,约定版本号只能加,不能减,每次操作余额,版本号都要+1,通过CAS判定版本号,就可以进一步的来完成上述的操作。
如果版本号没有改变,数据就一定没有变过。
通过上述CAS已经判定了,同一时刻只有一个线程能修改.
其他线程并发执行的时候,无法进入if条件.确保balance不会出现多线程同时修改的情况.
网址:锁策略及优化总结 https://www.yuejiaxmz.com/news/view/548822
相关内容
最优化算法——常见优化算法分类及总结网络优化总结
性能优化十条策略
小米 14 系列手机获推澎湃 HyperOS 1.0.42.0:新增出行助手、优化锁屏编辑触发策略
数码产品关键词的重要性及优化策略
PyTorch 节省显存的策略总结
优化学习的18个策略
酒店七夕活动策划方案总结(优秀15篇)
Windows服务器最长稳定运行时间,记录及优化策略
小学数学购物策略教学方法总结