reentrantlock_使用ReentrantLock和Lambdas进行干净同步

reentrantlock

reentrantlock

最近,我在阅读一篇内容丰富的文章,内容涉及Javin Paul 1 synchronizedReentrantLock之间的区别。 他强调了后者的优点,但并未保留一些缺点,这些缺点与正确使用所需的繁琐的try-finally块有关。

在同意他的陈述的同时,我沉迷于一个想法,当它涉及到同步时,总是会困扰我。 两种方法将单独的关注点混合在一起-同步和同步内容的功能-妨碍了逐一测试这些关注点。

作为探索性的类型,我为过去已经尝试过的此问题选择了一种解决方案。 但是那时我不太喜欢编程模式。 这是由于由于匿名类而导致的冗长。 但是,手头有Java 8和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-finally块,它在实际功能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();
    }
  }
}

在下面的类提取步骤中,引入了Synchronizer类型以用作执行程序,以确保在适当的同步范围内执行给定的Operation

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和“ Synchronizer已移至其自己的文件。 这样,提供了同步方面,并且可以作为单独的单元进行测试。 现在, Counter类使用构造函数注入同步器实例4 。 此外,增量操作已分配给名为“ incrementer”的字段。 为了简化测试,最终字段的可见性已默认打开。 使用Mockito进行例如监视同步器的测试现在可以确保正确的同步调用如下:

@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. ReentrantLock的实例在Java中,同步VS ReentrantLock的区别,Javin保罗,2013年3月7日
  2. 显然足够的噪声来迷惑我,因为我的第一个测试版失败...
  3. 我决定使用类型参数返回值而不是int 。 这样,可以更好地重用所得的同步机制。 但是我不确定由于性能或其他原因,例如自动装箱在这里是否不重要。 因此,对于一般方法,可能还有更多需要考虑的事情,尽管
  4. 如果由于某种原因无法更改构造函数,则可以引入一个委托的默认构造函数,该构造函数将Synchronizer的新实例注入到参数化的实例中,如下所示: this( new Synchronizer() ); 。 这种方法可能是用于测试目的可接受的开销

翻译自: https://www.javacodegeeks.com/2014/04/clean-synchronization-using-reentrantlock-and-lambdas.html

reentrantlock

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值