Java并发编程-锁的粒度与死锁 一时失言乱红尘 2022-12-27 08:43 168阅读 0赞 ### 锁的粒度 ### 今天拿取款来说说事儿 假设我们有Acount用户这个实体,有两个字段,一个用户名,一个余额,都是资源。 class Acount{ private Integer balance; private String name; public void updateName(String name){ this.name = name; } public void withdraw(Integer amt){ if(this.balance > amt){ this.balance -= amt; } } } #### 一把锁锁一个资源 #### 讲道理,如果我们现在想给用户名、余额分别加锁,我们需要这样做 class Acount{ private Integer balance; private final Object balanceLock=new Object(); private String name; private final Object nameLock=new Object(); public void updateName(String name){ synchronized(nameLock){ this.name = name; } } public void withdraw(Integer amt){ synchronized(balanceLock){ if(this.balance > amt){ this.balance -= amt; } } } } 这样分别给每个用户对象,分配了一把锁,多线程下修改同一用户信息,不会出现A、B线程都拿到资源在不同的CPU运行,出现不可见问题 #### 一把锁锁多个资源 #### synchronized可以用在变量、方法、静态方法上,分别对应锁的粒度为变量、当前对象this、当前类 当需要锁定当前对象多个方法时 class Acount{ private Integer balance; private String name; synchronized public void updateName(String name){ this.name = name; } synchronized public void withdraw(Integer amt){ if(this.balance > amt){ this.balance -= amt; } } } 这样就针对某个用户对象,同一时间updateName()、withdraw()只能有一个执行 如果现在需要转账,a给b转账,b给c转账,单独锁一个对象肯定是不够玩了,因为锁是针对资源锁定的,加在方法上只能锁定当前用户对象 这样做会有什么问题呢(代码在下面)? 我模拟一个场景,a\\b\\c各有100块钱,a给b转100,b给c转100,A、B线程同时执行。 A获取到,a对象的锁 B获取到,b对象的锁 但是他们没有锁定对方账户,那同时执行的后果,可能B线程b=0写完,A线程的b=200也写完了 最终结果a=0、b=200、c=200,与我们预想的a=0、b=100、c=200,凭空多了100块 class Acount{ private Integer balance; private String name; synchronized public void transfer(Acount target,Integer amt){ if(this.balance > amt){ this.balance -= amt; target.balance += amt; } } } 这种情况可以加static修饰transfer方法,这样锁的资源对象就是Acount.class了,但是这样的后果就是整个系统的转账串行化,效率低下! 我们再进阶一步优化锁 我们需要将目标对象也锁了 class Acount{ private Integer balance; private String name; synchronized public void transfer(Acount target,Integer amt){ synchronized(target){ if(this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } 这次,a->b ,b->c不会再出问题了,因为操作成了原子性的,只有同时获取到两把锁,才能执行业务,这保障了原子性 但是,如果a->b,b->a转账,就赶寸了,A线程拿到a锁,B拿到b锁,互相等对方手中的资源 没错,死锁了~ #### 避免死锁 #### 对于死锁的发生,我们要参考卡夫曼大佬的总结,先了解死锁的形成。 1. 互斥,同一时间只有一个线程可以拿到两把锁 2. 占有且等待,获取到部分资源,楞等别的资源 3. 不可抢占资源,别的线程不能抢走当前线程已经获取的部分资源 4. 循环等待,我也等、你也等 只要破坏其一,就可以解决死锁的问题,第一点,互斥,本身我们就是想让两个有关系的资源a、b账户对象绑一起,线程间互斥,所以pass第一个 ##### 占有且等待 ##### 一次让他拿到所有的资源,就可以解决,我们创建一个Allocator类,他管理所有的资源,开放apply()方法获取所有资源,free()释放资源 class Allocator{ private List<Acount> pool = new ArrayList(){ }; synchronized public boolean apply(Acount from,Acount to){ if(pool.contains(from)||pool.contains(to)){ return false; } pool.add(from); pool.add(to); return true; } synchronized public void free(Acount from,Acount to){ pool.remove(from); pool.remove(to); } } class Acount{ private Integer balance; private Allocator allocator; public void transfer(Acount target,Integer amt){ while(!allocator.apply(this,target)); try{ synchronized(this){ synchronized(target){ if(this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } finally{ allocator.free(this,target); } } } 这种方式可以保证执行可以多线程,但是获取资源仍然是串行的,部分可以高效 ##### 不可抢占 ##### synchronized目前做不到主动释放已占有资源,因为获取到资源,线程就进入阻塞了,啥都干不了。 需要用到concurrent包下的Lock可以搞定 ##### 资源循环 ##### 将我们需要锁的资源,按相同顺序排序,再上锁,破坏循环规则 class Acount{ private Integer balance; public void transfer(Acount target,Integer amt){ Acount left=this,right=target; if(this.id>target){ right = this; left = target; } synchronized(this){ synchronized(target){ if(this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } } 还有可以整个丢到mq里,让他获取锁,获取失败就重试,保证最终一致性
相关 Java并发编程问题:死锁与解决策略 在Java并发编程中,死锁是一个严重的问题。简单来说,死锁是两个或更多的进程因争夺资源而造成的一种互相等待的现象,若无外力干涉它们都将无法继续执行。 解决死锁的主要策略包括: 旧城等待,/ 2024年09月19日 16:21/ 0 赞/ 5 阅读
相关 Java并发编程:死锁示例 死锁是多线程并发编程中的一种严重问题,当一个或多个线程因等待其他线程释放已获得的资源而造成的一种相互“僵持”的状态时,就可能发生死锁。 下面是一个经典的死锁示例(使用Java 痛定思痛。/ 2024年09月18日 03:36/ 0 赞/ 21 阅读
相关 Java并发编程:死锁和避免死锁的方法 死锁是多线程编程中的一种严重问题,会导致所有线程都无法继续执行任务。以下是死锁的特征和一些避免死锁的方法: 1. **特征**: - **互斥**:资源被多个线程同时请 待我称王封你为后i/ 2024年09月11日 05:42/ 0 赞/ 19 阅读
相关 Java并发编程如何防止死锁? 由于 OBJECT_1 和 OBJECT_2 锁都没有被释放,所以两个线程会一起请求,陷入死循环,即出现死锁情况。当一个线程获得了 OBJECT_1 锁时,它并没有释放锁... 我就是我/ 2024年05月23日 22:03/ 0 赞/ 32 阅读
相关 JAVA并发编程——死锁 目录 什么是死锁? 动手写死锁 发现排查死锁情况 解决办法 每日寄语 -------------------- 什么是死锁? 死锁不仅在个人学习中,甚 曾经终败给现在/ 2024年03月25日 23:16/ 0 赞/ 47 阅读
相关 Java高并发编程实战12,越细粒度的锁越好吗?产生死锁怎么办? ![在这里插入图片描述][986dcebce006483fb6846cb29a3928a7.gif_pic_center] 目录 先说结论,可能会产生 叁歲伎倆/ 2024年02月28日 02:18/ 0 赞/ 9 阅读
相关 java并发编程—死锁 死锁 死锁是指两个或多个进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法继续执行下去。 死锁的解决方法有以下几种: 1. 避免死锁 淩亂°似流年/ 2023年10月12日 12:34/ 0 赞/ 25 阅读
相关 Java并发编程-锁的粒度与死锁 锁的粒度 今天拿取款来说说事儿 假设我们有Acount用户这个实体,有两个字段,一个用户名,一个余额,都是资源。 class Acount{ p 一时失言乱红尘/ 2022年12月27日 08:43/ 0 赞/ 169 阅读
相关 java并发编程:死锁代码示例 java并发编程:死锁代码示例 死锁概念: > 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将 灰太狼/ 2022年01月27日 12:13/ 0 赞/ 218 阅读
还没有评论,来说两句吧...