Page 48 - 《软件学报》2021年第12期
P. 48

3712                                Journal of Software  软件学报 Vol.32, No.12, December 2021

             本文第 1 节介绍本文的研究动机.第 2 节介绍面向细粒度锁的自动重构方法.第 3 节展示自动重构工具
         FLock 的使用界面.在第 4 节给出对本文所提出的方法和工具的实验评估.第 5 节对相关工作进行介绍.第 6 节
         是本文的总结.

         1    研究动机

             读写锁是 JDK 1.5 版本中引入的一种锁机制,它包含一对相互关联的锁:读锁和写锁.写锁是一个排它锁,只
         能由一个线程持有;读锁是一个共享锁,在没有线程持有写锁的情况下,读锁可以由多个线程同时持有,读锁允
         许在访问共享数据时有更大的并发性.Pinto 等人              [12] 通过研究 SourceForge 上的 2 227 个含有并发结构的 Java
         工程发现:Java 并发包还没有得到良好的应用,只有大约 23%有并发编程结构的 Java 工程在使用.
             为了说明细粒度锁重构的必要性,我们在代码结构上进行了说明.图 1 展示了 processCached(⋅)方法的 3 种
         实现方式,该程序段选自读写锁的 Java API 文档           [13] ,是一种典型的缓存处理操作.方法 processCached(⋅)模拟了对
         数据库及缓存的操作,首先判断数据是否存在于缓存中:如果存在,则直接从缓存中读取数据;如果不存在,则从
         数据库中把数据写入缓存.在图 1(a)中,该方法使用 synchronized 进行同步控制,整个方法都处于该锁的保护下,
                                              [5]
         是一种粗粒度的保护.图 1(b)为使用 Relocker 进行重构后的代码,由于该方法中包含对缓存的写入操作,按照
         Relocker 的锁推断策略,将使用写锁对其进行重构.然而我们发现:写操作的执行仅仅发生在 if 语句的条件成立
         时,如果条件不成立,写锁根本不会执行,只需要执行读操作.如果把全部代码使用写锁进行保护,可能会降低程
         序的性能.如果使用读锁,将允许多个线程同时读,可以提高程序的并发性.图 1(c)为改进后的代码,是一种细粒
         度的加锁方式.该方式首先获取读锁并判断 cacheValid(第 3 行、第 4 行):如果 if 条件不成立,则直接读取并释放
         读锁(第 16 行~第 20 行);如果成立,则释放读锁获得写锁(第 5 行、第 6 行).为了保证程序状态的一致性,这里需
         要重新对状态进行判断(第 8 行),因为其他线程可能已经对缓存进行了修改.当从数据库中写入缓存之后,获得
         读锁再释放写锁,完成锁降级操作(第 9 行~第 14 行).从图 1(c)可以看到:该方式将一直使用读锁,直到写入的时
         候再加写锁.



























                             Fig.1  Three implementations of the method processCached(⋅)
                                   图 1   processCached(⋅)方法的 3 种实现方式

             从上面的例子可以看出:使用锁降级实现了对临界区的细粒度保护,加锁方式更为合理,并且可以在一定程
         度上减少了锁竞争.由于读锁是共享锁,允许多个线程同时访问,在不发生写入时可以增加程序的并发性.
   43   44   45   46   47   48   49   50   51   52   53