但是有时我们需要对同步进行更多控制。 我们要么需要分别控制访问类型(读取和写入),要么使用起来很麻烦,因为要么没有明显的互斥锁,要么我们需要维护多个互斥锁。
值得庆幸的是,Java 1.5中添加了锁实用程序类,使这些问题更易于解决。
Java重入锁
Java在java.util.concurrent.locks包中有一些锁实现。
锁的一般类很好地布置为接口:
- 锁 –最简单的锁,可以获取和释放
- ReadWriteLock –具有读和写锁类型的锁实现–一次可以持有多个读锁,除非持有排他写锁
Java提供了我们关心的这些锁的两种实现–两者都是可重入的(这仅意味着线程可以多次重新获取同一锁而没有任何问题)。
- ReentrantLock –如您所料,可重入锁实现
- ReentrantReadWriteLock –可重入ReadWriteLock实现
现在,让我们看一些例子。
读/写锁示例
那么如何使用锁呢? 这很简单:只需获取并发布(永远不要忘记发布-终于是您的朋友!)。
假设我们有一个非常简单的情况,我们需要同步访问一对变量。 一个是简单的值,另一个是根据一些冗长的计算得出的。 首先,这就是我们如何使用synced关键字执行此操作。
public class Calculator {private int calculatedValue;private int value;public synchronized void calculate(int value) {this.value = value;this.calculatedValue = doMySlowCalculation(value);}public synchronized int getCalculatedValue() {return calculatedValue;}public synchronized int getValue() {return value;}
}
很简单,但是如果我们有很多争用或者执行大量读取而写入很少,则同步可能会影响性能。 由于频繁读取比写入频繁得多,因此使用ReadWriteLock可帮助我们最大程度地减少问题:
public class Calculator {private int calculatedValue;private int value;private ReadWriteLock lock = new ReentrantReadWriteLock();public void calculate(int value) {lock.writeLock().lock();try {this.value = value;this.calculatedValue = doMySlowCalculation(value);} finally {lock.writeLock().unlock();}}public int getCalculatedValue() {lock.readLock().lock();try {return calculatedValue;} finally {lock.readLock().unlock();}}public int getValue() {lock.readLock().lock();try {return value;} finally {lock.readLock().unlock();}}
}
该示例实际上显示了使用同步的has的一个大优点:与使用显式锁相比,此方法简洁明了且更加安全。 但是锁提供了使用灵活性,而这是我们以前所没有的。
在上面的示例中,我们可以让数百个线程一次读取相同的值而不会出现问题,并且只有在获得写入锁定时才阻塞读取器。 请记住:许多读取器可以同时获取读取锁定,但是在获取写入锁定时不允许读取器或写入器。
更典型的用途
我们的第一个示例可能会让您感到困惑或不完全相信显式锁是有用的。 难道他们还没有其他用途吗? 当然!
我们在Carfey使用显式锁来解决许多问题。 一个示例是您有可以同时运行的各种任务,但是您不希望同时运行多个相同类型的任务。 一种实现它的干净方法是使用锁。 可以通过同步来完成,但是锁使我们能够在超时后失败。
值得一提的是,您会注意到我们使用了同步锁和显式锁的组合-有时一个比另一个更干净,更简单。
public class TaskRunner {private Map<Class<? extends Runnable>, Lock> mLocks =new HashMap<Class<? extends Runnable>, Lock>();public void runTaskUniquely(Runnable r, int secondsToWait) {Lock lock = getLock(r.getClass());boolean acquired = lock.tryLock(secondsToWait, TimeUnit.SECONDS);if (acquired) {try {r.run();} finally {lock.unlock();}} else {// failure code here}}private synchronized Lock getLock(Class clazz) {Lock l = mLocks.get(clazz);if (l == null) {l = new ReentrantLock();mLocks.put(clazz, l);}return l;}
}
这两个示例应该使您对如何同时使用计划锁和ReadWriteLocks有所了解。 与同步一样,不必担心重新获得相同的锁-JDK中提供的锁是可重入的,因此不会有任何问题。
每当您处理并发时,都有危险。 永远记住以下几点:
- 释放finally块中的所有锁。 这是规则1,有一个原因。
- 当心线程饥饿! 如果您有不想永久等待的许多读者和偶尔的作家,那么ReentrantLocks中的公平设置可能会很有用。 如果其他线程不断持有读取锁,那么编写者可能会等待很长时间(可能永远)。
- 尽可能使用同步。 您将避免错误并保持代码清洁。
- 如果您不希望线程无限期等待获取锁,请使用tryLock() -这类似于数据库具有的等待锁超时。
就是这样! 如果您有任何问题或意见,请随时将其留在下面。
参考: Java并发第2部分–来自我们的JCG合作伙伴的Carent博客上的 Reentrant Locks 。
- Java并发教程–信号量
- Java并发教程–线程池
- Java并发教程–可调用,将来
- Java并发教程–阻塞队列
- Java并发教程– CountDownLatch
- Exchanger和无GC的Java
- Java Fork / Join进行并行编程
- Java最佳实践–队列之战和链接的ConcurrentHashMap
- 使用迭代器时如何避免ConcurrentModificationException
- 改善Java应用程序性能的快速技巧
翻译自: https://www.javacodegeeks.com/2011/09/java-concurrency-tutorial-reentrant.html