# 《并发设计模式》第11章-保护性暂挂模式-使用保护性暂挂模式优化交易系统性能

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

  • 本章难度:★★☆☆☆
  • 本章重点:掌握保护性暂挂模式的使用场景,理解保护性暂挂模式的核心原理与实现方式,并能够结合自身实际业务场景将保护性暂挂模式灵活应用到自身实际项目中。

大家好,我是冰河~~

在并发编程中,为了提升程序的并发度和性能,往往会将一个大的任务分解成多个小的子任务,并且将这些子任务交给多个线程执行,多个线程在执行子任务的过程中,会存在一个线程需要等待其他线程完成后才能继续执行的情况,而保护性暂挂模式就比较适合这种场景。

# 一、故事背景

老王看了小菜写的交易流程代码后,分析出小菜写的代码不只是性能问题,还存在加锁的线程安全性问题,并为小菜分不同的业务关系场景讲清楚了为何交易过程中会存在线程安全问题,也讲解了如何使用正确的方式加锁。同时,老王也为小菜讲解了如何进行锁优化,以及如何解决锁优化过程中产生的死锁问题。

对于小菜来说,学到了在多线程并发场景下,如何对锁进行优化,在某些场景下,为何会出现死锁,产生死锁的必要条件,以及如何预防死锁。

学完这些知识后,小菜就尝试去解决自己开发交易功能时,存在的线程安全问题,小菜采用的是破坏请求与保持条件的预防死锁方案,处理完后,再次找到老王,让其看看自己实现的代码还会不会存在其他问题。

# 二、出乎意料

小菜使用破坏请求与保持条件的预防死锁方案优化了自己实现的交易系统流程,这天来到公司后,发现老王已经坐在工位上正在处理工作。小菜走到自己的工位上,将电脑包放到办公桌上,便向老王说:“早啊,老大”。

老王抬头看到是小菜,便说了句:“之前讲的都明白了吧?交易的代码修改了吗?”。

小菜随即说到:“之前讲的都明白了,代码也已经优化完了,老大帮我看看还有啥问题,可以吗?”。

“可以”,老王随即离开座位走到了小菜的工位,看了看小菜写的代码后,说到:“使用破坏请求与保持条件的预防死锁的方案进行处理,可以是可以,只不过在并发量不是很大的时候,可以这样写,要是并发量上来就不能这么写了”。

小菜听后,一脸茫然,说到:“还有其他更优的方式吗?这个我就不太清楚了”。

老王笑了笑说到:“当然还有更优的实现方式,那就是使用保护性暂挂模式进行优化”。

“额,这个确实不知道,可以给我讲讲吗?”,小菜说道。

“可以,走,我们还是去会议室说”。

“好的”。

二人一起走进了会议室。

# 三、性能问题

使用破坏请求与保持条件的预防死锁的方案处理线程安全问题时,会存在性能问题,主要是因为在基于破坏请求与保持条件的预防死锁的方案处理线程安全问题时,会使用如下代码片段进行转账。

//转账操作
public void transfer(TansferAccount target, Integer transferMoney){
    //自旋申请转出账户和转入账户,直到成功
    while(!requester.applyResources(this, target)){
        //循环体为空
        ;
    }
    try{
        //对转出账户加锁
        synchronized(this){
            //对转入账户加锁
            synchronized(target){
                if(this.balance >= transferMoney){
                    this.balance -= transferMoney;
                    target.balance += transferMoney;
                }   
            }
        }
    }finally{
        //最后释放账户资源
        requester.releaseResources(this, target);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

性能瓶颈会出现在如下代码片段。

//自旋申请转出账户和转入账户,直到成功
while(!requester.applyResources(this, target)){
    //循环体为空
    ;
}
1
2
3
4
5

这种方式就是死循环的方式来循环等待,直到一次全部获取到两个锁后,才进行后面的转账操作。

这种方式在并发量不大的情况下,可以接受,但是一旦并发量增大,获取锁的冲突增加的时候,这种方案就不适合了,因为在这种场景下,很有可能要循环成千上万次才能获得锁,非常消耗系统性能,在高并发大流量的业务场景下很显然不合适。

在这种场景下,最好的方案就是使用Guarded Suspension模式,也就是保护性暂挂模式,在这种高并发设计模式下,如果线程A获取不到所有的锁,就阻塞自己,进入“等待WAITING”状态。当线程A要求的所有条件都满足后,通知阻塞的线程A继续执行。

# 四、保护性暂挂模式流程

基于保护性暂挂模式进行转账的流程如图11-1所示。

# 查看全文

加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码