lambda表达式
考虑到旧版Java代码,无论您在哪里看,带有lambda表达式的Java 8绝对可以提高质量和可读性。 今天,让我们看一下ReadWriteLock
以及如何使它使用起来更简单。 假设我们有一个称为Buffer
的类,它可以记住队列中的最后几条消息,对旧消息进行计数并丢弃。 实现非常简单:
public class Buffer {
private final int capacity;
private final Deque<String> recent;
private int discarded;
public Buffer(int capacity) {
this.capacity = capacity;
this.recent = new ArrayDeque<>(capacity);
}
public void putItem(String item) {
while (recent.size() >= capacity) {
recent.removeFirst();
++discarded;
}
recent.addLast(item);
}
public List<String> getRecent() {
final ArrayList<String> result = new ArrayList<>();
result.addAll(recent);
return result;
}
public int getDiscardedCount() {
return discarded;
}
public int getTotal() {
return discarded + recent.size();
}
public void flush() {
discarded += recent.size();
recent.clear();
}
}
现在我们可以putItem()
,但是内部recent
队列将仅保留最后一个capacity
元素。 但是,它也记住必须丢弃多少项以避免内存泄漏。 该类工作正常,但仅在单线程环境中有效。 我们使用不是线程安全的ArrayDeque
和非同步的int
。 尽管对int
读写是原子的,但不能保证更改在不同线程中可见。 同样,即使我们将线程安全的BlockingDeque
与AtomicInteger
一起使用,我们也仍然处于竞争状态的危险中,因为这两个变量彼此不同步。
一种方法是synchronize
所有方法,但这似乎有很大的局限性。 此外,我们怀疑读取的数量大大超过写入的数量。 在这种情况下, ReadWriteLock
是绝佳的选择。 它实际上包括两个锁-一个用于读取,一个用于写入。 实际上,它们都为同一把锁竞争,而同一把锁可以同时由一个作者或多个读者获得。 因此,当没有人写并且只有写者偶尔阻止所有读者时,我们可以进行并发读取。 使用synchronized
将始终阻止所有其他对象,无论他们做什么。 ReadWriteLock
的可悲部分是它引入了许多样板。 您必须显式打开一个锁,并记住在finally
块中对其进行unlock()
。 我们的实现变得难以阅读:
public class Buffer {
private final int capacity;
private final Deque<String> recent;
private int discarded;
private final Lock readLock;
private final Lock writeLock;
public Buffer(int capacity) {
this.capacity = capacity;
recent = new ArrayDeque<>(capacity);
final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
readLock = rwLock.readLock();
writeLock = rwLock.writeLock();
}
public void putItem(String item) {
writeLock.lock();
try {
while (recent.size() >= capacity) {
recent.removeFirst();
++discarded;
}
recent.addLast(item);
} finally {
writeLock.unlock();
}
}
public List<String> getRecent() {
readLock.lock();
try {
final ArrayList<String> result = new ArrayList<>();
result.addAll(recent);
return result;
} finally {
readLock.unlock();
}
public int getDiscardedCount() {
readLock.lock();
try {
return discarded;
} finally {
readLock.unlock();
}
}
public int getTotal() {
readLock.lock();
try {
return discarded + recent.size();
} finally {
readLock.unlock();
}
}
public void flush() {
writeLock.lock();
try {
discarded += recent.size();
recent.clear();
} finally {
writeLock.unlock();
}
}
}
这是在8月8日之前完成的方式。有效,安全且丑陋。 但是,使用lambda表达式,我们可以将横切关注点包装在这样的实用程序类中:
public class FunctionalReadWriteLock {
private final Lock readLock;
private final Lock writeLock;
public FunctionalReadWriteLock() {
this(new ReentrantReadWriteLock());
}
public FunctionalReadWriteLock(ReadWriteLock lock) {
readLock = lock.readLock();
writeLock = lock.writeLock();
}
public <T> T read(Supplier<T> block) {
readLock.lock();
try {
return block.get();
} finally {
readLock.unlock();
}
}
public void read(Runnable block) {
readLock.lock();
try {
block.run();
} finally {
readLock.unlock();
}
}
public <T> T write(Supplier<T> block) {
writeLock.lock();
try {
return block.get();
} finally {
writeLock.unlock();
}
public void write(Runnable block) {
writeLock.lock();
try {
block.run();
} finally {
writeLock.unlock();
}
}
}
如您所见,我们包装了ReadWriteLock
并提供了一组可使用的实用程序方法。 原则上,我们希望传递Runnable
或Supplier<T>
(具有单个T get()
方法的接口),并确保调用它时已被适当的锁包围。 我们可以编写完全相同的包装器类,而无需使用lambda,但是使用它们可以大大简化客户端代码:
public class Buffer {
private final int capacity;
private final Deque<String> recent;
private int discarded;
private final FunctionalReadWriteLock guard;
public Buffer(int capacity) {
this.capacity = capacity;
recent = new ArrayDeque<>(capacity);
guard = new FunctionalReadWriteLock();
}
public void putItem(String item) {
guard.write(() -> {
while (recent.size() >= capacity) {
recent.removeFirst();
++discarded;
}
recent.addLast(item);
});
}
public List<String> getRecent() {
return guard.read(() -> {
return recent.stream().collect(toList());
});
}
public int getDiscardedCount() {
return guard.read(() -> discarded);
}
public int getTotal() {
return guard.read(() -> discarded + recent.size());
}
public void flush() {
guard.write(() -> {
discarded += recent.size();
recent.clear();
});
}
}
看看我们如何通过传递应该被保护的代码来调用guard.read()
和guard.write()
? 看起来很整洁。 顺便说一句,您是否注意到我们如何使用stream()
将任何集合变成任何其他集合(在这里: Deque
into List
stream()
? 现在,如果我们提取几个内部方法,则可以使用方法引用来进一步简化lambda:
public void flush() {
guard.write(this::unsafeFlush);
}
private void unsafeFlush() {
discarded += recent.size();
recent.clear();
}
public List<String> getRecent() {
return guard.read(this::defensiveCopyOfRecent);
}
private List<String> defensiveCopyOfRecent() {
return recent.stream().collect(toList());
}
这只是利用lambda表达式来改进现有代码和库的众多方法之一。 我们真的很高兴他们终于进入Java语言了,同时已经出现在其他数十种JVM语言中。
翻译自: https://www.javacodegeeks.com/2014/03/simplifying-readwritelock-with-java-8-and-lambdas.html
lambda表达式