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

java admin 47400 0 评论

最近我在读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 );
 } } 

如此以来,OperationSyschronizer都被移动到了单独的文件中。通过这种方式,同步方面的性能提高了,并且可以分开进行单元测试了。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表达式的一个热身活动,可能还忽略了并发性的内容——你觉得的呢?

注:

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

关于作者: 赖 信涛

(了解我更多,在:赖信涛的个人网站

查看赖 信涛的更多文章 >>

转载请注明: 飞嗨_分享互联网 » 使用ReentrantLock和Lambda表达式让同步更纯净

赞 (0) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽

高效,专业,符合SEO

联系我们