Java并发编程-锁的粒度与死锁

锁的粒度

今天拿取款来说说事儿
假设我们有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{
			synchronizedthis{
				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;
		}
			synchronizedthis{
				synchronized(target){
					if(this.balance > amt){
						this.balance -= amt;
						target.balance += amt;
					}
				}
			}
	}
}

还有可以整个丢到mq里,让他获取锁,获取失败就重试,保证最终一致性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值