使用ReentrantLock和Lambda表达式让同步更纯净

最近我在读Javin Paul的一篇文章,是关于synchronized和ReentrantLock的区别的[注1]。文章强调了后者的优势,但是也保留了一些缺点,笨重的try-final代码块需要谨慎使用。

同意他的观点的同时,也存在着一些困惑。每当涉及到同步时我都会想起这些困惑——对于这两种方法都不能对问题进行单独分析,因为同步是将同步的内容封装到函数里面的,这就不能分开的测试一些问题了。

为了探讨这个问题,我采用了以前尝试过的一个方法。尽管这种时候我不喜欢编程的部分。主要是不喜欢冗长的匿名类。但是现在有了Java8和Lambda表达式,就值得一试了。所以我用了Javin Paul的例子中“计数器”的部分写了一个测试用例,并开始重构,这是初步的样子:

可以清楚地看到,try-final那丑陋的代码块给实际的函数带来了很多杂乱的东西[注2]。方法是:将这段代码块封装在单独的类中,作为同步的一部分,并对外提供增加同步代码的方法。下面的部分展示了新创建的Operation接口的样子,以及它如何使用Lambda表达式[注3]:

在下面的类提取步骤中,声明了Syschronizer类,以确保给的Opreation在同步的范围内进行了适当的操作:

如果我没错的话,这应该是作为一个初始类。测试通过了,虽然JUnit测试在并发方面的测试不全面,但最后的一点修改至少保证了在单元测试的并发中顺序是合理的。

如此以来,OperationSyschronizer都被移动到了单独的文件中。通过这种方式,同步方面的性能提高了,并且可以分开进行单元测试了。Counter类现在使用了构造函数来传入一个Syschronizer实例[注4]。此外,添加操作独立封装成”incrementer”。但是测试时,final值没有是公开的,为了减少违背原则,使用Mockito的办法对Syschronizer进行优化以确保合适的调用:

鉴于单元测试和测试用例之间的紧密耦合,通常我不过分退出调用方法验证。但如果有紧急情况,这样做也不算太坏。在此,我仅仅做了Java 8和Lambda表达式的一个热身活动,可能还忽略了并发性的内容——你觉得的呢?

注:

  1. Java的ReentrantLock示例,synchronized和ReentrantLock之间的不同之处,Javin Paul,2013年3月7日。
  2. 因为我第一个版本的失败,这些乱七八糟的东西使我心烦。
  3. 我决定返回一个参数来替代int,这样,同步机制就可以更好地重用。但是我不确定在这里由于性能或者其他原因,自动装箱会不会不加判断的重用,所以对于一个通用的方法,这里还有很多要考虑的地方,这就不是本文所涉及的范围了……
  4. 修改构造函数最不可能的目的可能就是,向默认的构造器引入一个Syschronized的示例,像this( new Syschronized() );,但是出于测试目的,这是可以接受的。

本文翻译自 javacodegeeks



Leave a comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注