考虑到旧版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日之前完成的方式。有效,安全且丑陋。 但是,使用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