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

ImportNew  •  扫码分享
我是创始人李岩:很抱歉!给自己产品做个广告,点击进来看看。  
分享到:


本文由 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 );
  }
}

如此以来,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
[ 转载请保留原文出处、译者和译文链接。]



相关文章

  • Java 8:CompletableFuture终极指南
  • JDK8新增便利的Map默认值方法
  • 为什么Java 8存在接口污染
  • Java 8会给你的代码带来什么:一个实际的例子
  • Java8采用Martin Fowler的方法创建内部DSL
  • Java8-本地缓存
  • 据传Google在开发基于Android操作系统的游戏机
  • 深入JVM的Class文件结构
  • 单例模式中为什么用枚举更好
  • WAR包 vs 集成服务器的Java应用

随意打赏

提交建议
微信扫一扫,分享给好友吧。