使用ReentrantLock和Lambda表达式让同步更纯净
本文由 ImportNew - 赖 信涛 翻译自 javacodegeeks。欢迎加入Java小组。转载请参见文章末尾的要求。
最近我在读Javin Paul的一篇文章,是关于synchronized和ReentrantLock的区别的[注1]。文章强调了后者的优势,但是也保留了一些缺点,笨重的try-final代码块需要谨慎使用。
在赞同他的观点同时,我还存留着一些困惑。每当涉及到同步时,我都会想起这些困惑——对于这两种方法都不能对问题进行单独分析,因为同步是将同步的内容封装到函数里面的,这就不能对一些问题分别测试了。
为了探讨这个问题,我采用了以前尝试过的一个方法。尽管这种时候我不喜欢编程的部分。主要是我不大喜欢冗长的匿名类。但是现在有了Java8和Lambda表达式,就值得一试了。所以我用了Javin Paul的例子中“计数器”的部分写了一个测试用例,并开始重构。起初的代码是这样的:
class Counter { private final Lock lock; private int count; Counter() { lock = new ReentrantLock(); } int next() { lock.lock(); try { return count++; } finally { lock.unlock(); } } }
可以清楚地看到,try-final那丑陋的代码块给实际的函数带来了很多杂乱的东西[注2]。解决方法:将这段代码块封装在单独的类中,作为同步的一部分,并对外提供增加同步代码的方法。下面展示了新创建的Operation
接口,以及它如何使用Lambda表达式[注3]:
class Counter { private final Lock lock; private int count; interface Operation<T> { T execute(); } Counter() { lock = new ReentrantLock(); } int next() { lock.lock(); try { Operation<Integer> operation = () -> { return count++; }; return operation.execute(); } finally { lock.unlock(); } } }
在下面的类提取步骤中,声明了Syschronizer
类,以确保给的Opreation
在同步的范围内进行了适当的操作:
class Counter { private final Synchronizer synchronizer; private int count; interface Operation<T> { T execute(); } static class Synchronizer { private final Lock lock; Synchronizer() { lock = new ReentrantLock(); } private int execute( Operation<Integer> operation ) { lock.lock(); try { return operation.execute(); } finally { lock.unlock(); } } } Counter() { synchronizer = new Synchronizer(); } int next() { return synchronizer.execute( () -> { return count++; } ); } }
如果没错的话,这应该是作为一个初始类。测试顺利通过了,虽然JUnit测试在并发方面的测试不全面,但最后的一点修改至少保证了在单元测试的并发中顺序是合理的。
public class Counter { final Synchronizer<Integer> synchronizer; final Operation<Integer> incrementer; private int count; public Counter( Synchronizer<Integer> synchronizer ) { this.synchronizer = synchronizer; this.incrementer = () -> { return count++; }; } public int next() { return synchronizer.execute( incrementer ); } }
如此以来,Operation
和Syschronizer
都被移动到了单独的文件中。通过这种方式,同步方面的性能提高了,并且可以分开进行单元测试了。Counter
类现在使用了构造函数来传入一个Syschronizer
实例[注4]。此外,添加操作独立封装成”incrementer”。但是测试时,final值没有是公开的。为了避免违背原则,使用Mockito的办法对Syschronizer
进行优化以确保合适的调用:
@Test public void synchronization() { Synchronizer<Integer> synchronizer = spy( new Synchronizer<>() ); Counter counter = new Counter( synchronizer ); counter.next(); verify( synchronizer ).execute( counter.incrementer ); }
鉴于单元测试和测试用例之间的紧密耦合,通常我不过分退出调用方法验证。但如果有紧急情况,这样做也不算太坏。在此,我仅仅做了Java 8和Lambda表达式的一个热身活动,可能还忽略了并发性的内容——你觉得的呢?
注:
- Java的ReentrantLock示例,synchronized和ReentrantLock之间的不同之处,Javin Paul,2013年3月7日。
- 因为我第一个版本的失败,这些乱七八糟的东西使我心烦。
- 我决定返回一个参数来替代int,这样,同步机制就可以更好地重用。但是我不确定在这里由于性能或者其他原因,自动装箱会不会不加判断的重用,所以对于一个通用的方法,这里还有很多要考虑的地方,这就不是本文所涉及的范围了……
- 修改构造函数最不可能的目的可能就是,向默认的构造器引入一个
Syschronized
的示例,像this( new Syschronized() );
,但是出于测试目的,这是可以接受的。
原文链接: javacodegeeks 翻译: ImportNew.com - 赖 信涛
译文链接: http://www.importnew.com/11585.html
[ 转载请保留原文出处、译者和译文链接。]
相关文章
- Java 8:CompletableFuture终极指南
- JDK8新增便利的Map默认值方法
- 为什么Java 8存在接口污染
- Java 8会给你的代码带来什么:一个实际的例子
- Java8采用Martin Fowler的方法创建内部DSL
- Java8-本地缓存
- 据传Google在开发基于Android操作系统的游戏机
- 深入JVM的Class文件结构
- 单例模式中为什么用枚举更好
- WAR包 vs 集成服务器的Java应用