库存-并发学习笔记 (2)

原子性问题的源头是线程切换,如果能够禁用线程切换那不就能解决这个问题了吗?而操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换。

同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。

synchronized

synchronized属于重量级锁,性能不高,在锁竞争激烈的场所不建议使用

synchronized好处在于简单易用,绝对不会unlock

锁和受保护资源的关系

受保护资源和锁之间的关联关系是 N:1 的关系

重点

synchronized锁膨胀过程

synchronized 对象头 monitor

long类型的并发读写问题(long64位 -- 32位操作系统)

金句

单核时代通过控制线程的切花就可以保证原子性,但是在多核时代,单纯的控制线程切换是无法保证原子性的,需要通过锁的互斥来
保证高并发场景下的原子性。

04 | 互斥锁(下):如何用一把锁保护多个资源

笔记

保护没有关联关系的多个资源

用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁

保护有关联关系的多个资源

锁能覆盖所有受保护资源

对象锁无法解决这个问题,因为会产生,我家的锁锁住别人家的资源的情况

正确姿势是采用类锁(性能有待优化)

理解

以前没考虑过也没遇到过 同一把锁管理多个资源的情况,以后在用锁的场景需要注意。

05 | 一不小心就死锁了,怎么办

笔记

死锁的专业定义
一组线程因为竞争共享资源而陷入互相等待,导致“永久”阻塞的现象。class Account { private int balance; // 转账 void transfer(Account target, int amt){ // 锁定转出账户 synchronized(this){ //① // 锁定转入账户 synchronized(target){ //② if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } } }

在现实中寻找答案
我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账

文件架上恰好有转出账本和转入账本,那就同时拿走;

如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其他柜员把另外一个账本送回来;

转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。
死锁产生
同一时刻A柜员拿到了入账账本,B柜员拿到了出账账本,A等待出账账本,B等待入账账本。AB柜员就会陷入“永久”等待。这就是死锁。

粗粒度锁
解决上述问题,可以采用粗粒度锁,也就是类锁,但是类锁带来的是性能问题。

细粒度锁

优点:使用细粒度锁可以提高并行度,是性能优化的有效手段。

风险:机会和风险是并存的。细粒度锁可能导致死锁。

如何预防死锁
解决死锁最好的办法是预防死锁,将其扼杀在摇篮里。

产生死锁的四个条件

互斥,共享资源X和Y只能被一个线程占用

占有且等待,线程T1占有X资源,在等待资源Y的同时,不释放资源X。

不可抢占,其他线程不能强行抢占线程T1占有的资源

循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待

解决死锁的思路就很简单了,就是破坏以上一个条件就不会造成死锁

破坏占用且等待条件:一次性申请所有的资源

example:不允许柜员直接在文件架上拿账本,而是增加管理员,柜员拿账本需要通过管理员来审核。比如,A柜员需要拿进账
管理员发现文件架上没有出账账本,所以不允许柜员只拿进账账本。这样就解决了占用且等待问题。

破坏不可抢占条件:核心是要能够主动释放它占有的资源

这一点 synchronized 是做不到的。因为synchronized一旦申请不到资源就会进入阻塞状态
进入阻塞态就以为什么也干不了,也释放不了线程占用的资源。

ReetrentLock 可以解决这个问题

破坏循环等待条件:对资源排序,然后按序申请资源

我们假设每个账户都有不同的属性 id,这个 id 可以作为排序字段,申请的时候,我们可以按照从小到大的顺序来申请

class Account { private int id; private int balance; // 转账 void transfer(Account target, int amt){ Account left = this; //① Account right = target; //② if (this.id > target.id) { //③ left = target; //④ right = this; //⑤ } //⑥ // 锁定序号小的账户 synchronized(left){ // 锁定序号大的账户 synchronized(right){ if (this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } }

金句

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygwfg.html