原子性问题的源头是线程切换,如果能够禁用线程切换那不就能解决这个问题了吗?而操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换。
同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。
synchronized
synchronized属于重量级锁,性能不高,在锁竞争激烈的场所不建议使用
synchronized好处在于简单易用,绝对不会unlock
锁和受保护资源的关系
受保护资源和锁之间的关联关系是 N:1 的关系
重点
synchronized锁膨胀过程
synchronized 对象头 monitor
long类型的并发读写问题(long64位 -- 32位操作系统)
金句
单核时代通过控制线程的切花就可以保证原子性,但是在多核时代,单纯的控制线程切换是无法保证原子性的,需要通过锁的互斥来
保证高并发场景下的原子性。
笔记
保护没有关联关系的多个资源
用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁
保护有关联关系的多个资源
锁能覆盖所有受保护资源
对象锁无法解决这个问题,因为会产生,我家的锁锁住别人家的资源的情况
正确姿势是采用类锁(性能有待优化)
理解
以前没考虑过也没遇到过 同一把锁管理多个资源的情况,以后在用锁的场景需要注意。
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; } } } } }金句