java 并发锁
但是有时我们需要对同步进行更多控制。 我们要么需要分别控制访问类型(读取和写入),要么使用起来很麻烦,因为要么没有明显的互斥锁,要么我们需要维护多个互斥锁。
幸运的是,在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中提供的锁是可重入的,因此不会有任何问题。
每当您处理并发时,都有危险。 永远记住以下几点:
- 释放所有锁,使其最终锁住。 这是规则1,是有原因的。
- 当心线程饥饿! 如果您有不想永久等待的许多读者和偶尔的作家,那么ReentrantLocks中的公平设置可能会很有用。 如果其他线程不断持有读取锁,那么编写者有可能等待很长时间(也许永远)。
- 尽可能使用同步。 您将避免错误并保持代码更清洁。
- 如果您不希望线程无限期等待获取锁,请使用tryLock() -这类似于数据库具有的等待锁超时。
就是这样! 如果您有任何问题或意见,请随时将其留在下面。
参考: Java并发第2部分–来自JCG合作伙伴的Carent博客上的 Reentrant Locks 。
翻译自: https://www.javacodegeeks.com/2011/09/java-concurrency-tutorial-reentrant.html
java 并发锁