最近,我在阅读一篇内容丰富的文章,内容涉及Javin Paul 1 synchronized
和ReentrantLock
之间的区别。 他强调了后者的优点,但并未保留一些缺点,这些缺点与正确使用所需的繁琐的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表达式进行第一次热身,也许我在并发方面也缺少一些东西-那么您怎么看?
- ReentrantLock的实例在Java中,同步VS ReentrantLock的区别 ,Javin保罗,2013年3月7日↩
- 显然足够的噪声来迷惑我,因为我的第一个测试版失败... ↩
- 我决定使用类型参数返回值而不是
int
。 这样,可以更好地重用所得的同步机制。 但是我不确定由于性能或其他原因,例如自动装箱在这里是否很重要。 因此,对于一般方法来说,可能还有更多需要考虑的事情,尽管things - 如果由于某种原因无法更改构造函数,则可以引入一个委托的默认构造函数,该构造函数将
Synchronizer
的新实例注入到参数化的实例中,如下所示:this( new Synchronizer() );
。 这种方法可能是用于测试目的的可接受的开销↩
翻译自: https://www.javacodegeeks.com/2014/04/clean-synchronization-using-reentrantlock-and-lambdas.html