![reentrantlock](https://i-blog.csdnimg.cn/blog_migrate/30e501fac4f1f1144cbcebab2f304dbc.png)
reentrantlock
最近,我在阅读一篇内容丰富的文章,内容涉及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
。 这样,可以更好地重用所得的同步机制。 但是我不确定由于性能或其他原因,例如自动装箱在这里是否不重要。 因此,对于一般方法,可能还有更多需要考虑的事情,尽管↩ - 如果由于某种原因无法更改构造函数,则可以引入一个委托的默认构造函数,该构造函数将
Synchronizer
的新实例注入到参数化的实例中,如下所示:this( new Synchronizer() );
。 这种方法可能是用于测试目的可接受的开销↩
翻译自: https://www.javacodegeeks.com/2014/04/clean-synchronization-using-reentrantlock-and-lambdas.html
reentrantlock