简单地说,锁是一种比标准同步块更灵活、更复杂的线程同步机制。
Lock接口从 Java 1.5 开始就已经存在了。它是在java.util.concurrent.lock包中定义的,它提供了广泛的锁定操作。
在本教程中,我们将探讨Lock接口的不同实现及其应用程序。
使用同步块和使用锁定API 之间存在一些区别:
让我们看一下Lock接口中的方法:
锁定的实例应始终解锁以避免死锁情况。
使用锁的推荐代码块应包含try/catch和finally块:
Lock lock = ...;
lock.lock();
try {
// access to the shared resource
} finally {
lock.unlock();
}Copy除了 Lock 接口之外,我们还有一个ReadWriteLock接口,它维护一对锁,一个用于只读操作,一个用于写入操作。只要没有写入,读锁定就可以由多个线程同时持有。
ReadWriteLock声明了获取读锁或写锁的方法:
ReentrantLock类实现Lock接口。它提供与使用同步方法和语句访问的隐式监视器锁相同的并发性和内存语义,并具有扩展功能。
让我们看看如何使用ReentrantLock进行同步:
public class SharedObject {
//...
ReentrantLock lock = new ReentrantLock();
int counter = 0;
public void perform() {
lock.lock();
try {
// Critical section here
count++;
} finally {
lock.unlock();
}
}
//...
}Copy我们需要确保将lock() 和unlock() 调用包装在try-finally块中,以避免死锁情况。
让我们看看tryLock() 是如何工作的:
public void performTryLock(){
//...
boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
if(isLockAcquired) {
try {
//Critical section here
} finally {
lock.unlock();
}
}
//...
}
Copy在这种情况下,调用tryLock() 的线程将等待一秒钟,如果锁不可用,则会放弃等待。
ReentrantReadWriteLock类实现ReadWriteLock接口。
让我们看看通过线程获取ReadLock或WriteLock的规则:
让我们看看如何使用读写锁:
public class SynchronizedHashMapWithReadWriteLock {
Map<String,String> syncHashMap = new HashMap<>();
ReadWriteLock lock = new ReentrantReadWriteLock();
// ...
Lock writeLock = lock.writeLock();
public void put(String key, String value) {
try {
writeLock.lock();
syncHashMap.put(key, value);
} finally {
writeLock.unlock();
}
}
...
public String remove(String key){
try {
writeLock.lock();
return syncHashMap.remove(key);
} finally {
writeLock.unlock();
}
}
//...
}Copy对于这两种写入方法,我们需要用写锁包围关键部分 — 只有一个线程可以访问它:
Lock readLock = lock.readLock();
//...
public String get(String key){
try {
readLock.lock();
return syncHashMap.get(key);
} finally {
readLock.unlock();
}
}
public boolean containsKey(String key) {
try {
readLock.lock();
return syncHashMap.containsKey(key);
} finally {
readLock.unlock();
}
}Copy对于这两种读取方法,我们需要用读锁定包围关键部分。如果没有正在进行的写入操作,则多个线程可以访问此部分。
StampedLock是在Java 8中引入的。它还支持读锁和写锁。
但是,锁获取方法返回一个标记,用于释放锁定或检查锁定是否仍然有效:
public class StampedLockDemo {
Map<String,String> map = new HashMap<>();
private StampedLock lock = new StampedLock();
public void put(String key, String value){
long stamp = lock.writeLock();
try {
map.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}
public String get(String key) throws InterruptedException {
long stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
}CopyStampedLock提供的另一个功能是乐观锁定。大多数情况下,读取操作不需要等待写入操作完成,因此不需要成熟的读锁定。
相反,我们可以升级到读锁定:
public String readWithOptimisticLock(String key) {
long stamp = lock.tryOptimisticRead();
String value = map.get(key);
if(!lock.validate(stamp)) {
stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlock(stamp);
}
}
return value;
}CopyCondition类使线程能够在执行关键部分时等待某些条件发生。
当线程获取对关键部分的访问权限但没有执行其操作的必要条件时,可能会发生这种情况。例如,读取器线程可以访问仍然没有任何数据可供使用的共享队列的锁。
传统上,Java 提供 wait()、notify() 和notifyAll() 方法来进行线程互通。
条件s 具有类似的机制,但我们也可以指定多个条件:
public class ReentrantLockWithCondition {
Stack<String> stack = new Stack<>();
int CAPACITY = 5;
ReentrantLock lock = new ReentrantLock();
Condition stackEmptyCondition = lock.newCondition();
Condition stackFullCondition = lock.newCondition();
public void pushToStack(String item){
try {
lock.lock();
while(stack.size() == CAPACITY) {
stackFullCondition.await();
}
stack.push(item);
stackEmptyCondition.signalAll();
} finally {
lock.unlock();
}
}
public String popFromStack() {
try {
lock.lock();
while(stack.size() == 0) {
stackEmptyCondition.await();
}
return stack.pop();
} finally {
stackFullCondition.signalAll();
lock.unlock();
}
}
}Copy在本文中,我们看到了 Lock 接口和新引入的StampedLock类的不同实现。
我们还探讨了如何利用Condition类来处理多个条件。