【高并发】面试官:讲讲高并发场景下如何优化加锁方式? (2)

(3)wait()、notify()和notifyAll()方法操作的队列是互斥锁的等待队列,如果synchronized锁定的是this对象,则一定要使用this.wait()、this.notify()和this.notifyAll()方法;如果synchronized锁定的是target对象,则一定要使用target.wait()、target.notify()和target.notifyAll()方法。

(4)wait()、notify()和notifyAll()方法调用的前提是已经获取了相应的互斥锁,也就是说,wait()、notify()和notifyAll()方法都是在synchronized方法中或代码块中调用的。如果在synchronized方法外或代码块外调用了三个方法,或者锁定的对象是this,使用target对象调用三个方法的话,JVM会抛出java.lang.IllegalMonitorStateException异常。

具体实现 实现逻辑

在实现之前,我们还需要考虑以下几个问题:

选择哪个互斥锁

在之前的程序中,我们在TansferAccount类中,存在一个ResourcesRequester 类的单例对象,所以,我们是可以使用this作为互斥锁的。这一点大家需要重点理解。

线程执行转账操作的条件

转出账户和转入账户都没有被分配过。

线程什么时候进入等待状态

线程继续执行需要的条件不满足的时候,进入等待状态。

什么时候通知等待的线程执行

当存在线程释放账户的资源时,通知等待的线程继续执行。

综上,我们可以得出以下核心代码。

while(不满足条件){ wait(); }

那么,问题来了!为何是在while循环中调用wait()方法呢?因为当wait()方法返回时,有可能线程执行的条件已经改变,也就是说,之前条件是满足的,但是现在已经不满足了,所以要重新检验条件是否满足。

实现代码

我们优化后的ResourcesRequester类的代码如下所示。

public class ResourcesRequester{ //存放申请资源的集合 private List<Object> resources = new ArrayList<Object>(); //一次申请所有的资源 public synchronized void applyResources(Object source, Object target){ while(resources.contains(source) || resources.contains(target)){ try{ wait(); }catch(Exception e){ e.printStackTrace(); } } resources.add(source); resources.add(targer); } //释放资源 public synchronized void releaseResources(Object source, Object target){ resources.remove(source); resources.remove(target); notifyAll(); } }

生成ResourcesRequester单例对象的Holder类ResourcesRequesterHolder的代码如下所示。

public class ResourcesRequesterHolder{ private ResourcesRequesterHolder(){} public static ResourcesRequester getInstance(){ return Singleton.INSTANCE.getInstance(); } private enum Singleton{ INSTANCE; private ResourcesRequester singleton; Singleton(){ singleton = new ResourcesRequester(); } public ResourcesRequester getInstance(){ return singleton; } } }

执行转账操作的类的代码如下所示。

public class TansferAccount{ //账户的余额 private Integer balance; //ResourcesRequester类的单例对象 private ResourcesRequester requester; public TansferAccount(Integer balance){ this.balance = balance; this.requester = ResourcesRequesterHolder.getInstance(); } //转账操作 public void transfer(TansferAccount target, Integer transferMoney){ //一次申请转出账户和转入账户,直到成功 requester.applyResources(this, target)) try{ //对转出账户加锁 synchronized(this){ //对转入账户加锁 synchronized(target){ if(this.balance >= transferMoney){ this.balance -= transferMoney; target.balance += transferMoney; } } } }finally{ //最后释放账户资源 requester.releaseResources(this, target); } } }

可以看到,我们在程序中通知处于等待状态的线程时,使用的是notifyAll()方法而不是notify()方法。那notify()方法和notifyAll()方法两者有什么区别呢?

notify()和notifyAll()的区别

notify()方法

随机通知等待队列中的一个线程。

notifyAll()方法

通知等待队列中的所有线程。

在实际工作过程中,如果没有特殊的要求,尽量使用notifyAll()方法。因为使用notify()方法是有风险的,可能会导致某些线程永久不会被通知到!

重磅福利

微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天阅读超硬核技术干货,公众号内回复【PDF】有我准备的一线大厂面试资料和我原创的超硬核PDF技术文档,以及我为大家精心准备的多套简历模板(不断更新中),希望大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。如果有幸我们江湖再见!

另外,我开源的各个PDF,后续我都会持续更新和维护,感谢大家长期以来对冰河的支持!!

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

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