java concurrent 锁_一文秒懂 Java java.util.concurrent.Locks

对于 Java 来讲,锁 ( Lock ) 是一种比标准同步块 ( synchronized block ) 更灵活,更复杂的线程同步机制。

其实,Java 1.5 就已经存在 Lock 接口了。这个 Lock 接口在 java.util.concurrent.lock 包中定义,提供了大量的锁操作。

本文中,我们将讲解 Lock 接口的不同实现并介绍如何在应用程序中使用锁。

锁 ( lock ) 和同步块 ( synchronized block ) 之间的差异

使用 synchronized 块和使用 Lock API 之间几乎没有区别:

同步块完全包含在方法中 : 在独立的方法中,我们可以使用 Lock 提供的 lock() 和 unlock() 实现锁和解锁操作。

同步块不支持公平竞争,任何线程都可以获取释放的锁定,且不能指定优先级。但锁 ( Lock ) 就不一样了,可以通过指定公平属性来实现 Lock 中的公平性。这可以确保最长的等待线程被授予锁定权限。

如果线程无法访问同步块,则会阻塞该线程。Lock 则提供了 tryLock() 方法。线程只有在可用且不被任何其他线程保持时才获取锁定。这减少了线程等待锁定的阻塞时间。

处于 「 等待 」 状态以获取对同步块的访问的线程不能被中断。Lock 提供了一个 lockInterruptibly() 方法,可用于在等待锁定时中断线程。

从上面的对比来看,同步块的所有机制,锁 ( Lock ) 都有相应的 API 对应。

Lock API

我们来看看 Lock 接口提供了哪些方法:

方法

说明

voidlock()

尝试获取锁(如果可用),如果锁不可用,则线程会被阻塞,直到锁被释放

oidlockInterruptibly()

类似于lock(),但它允许被阻塞的线程被中断并通过抛出的java.lang.InterruptedException恢复执行

booleantryLock()

lock()方法的非阻塞版本,它会立即尝试获取锁定,如果锁定成功则返回true

booleantryLock(longtimeout,TimeUnittimeUnit)

类似于tryLock(),但它可以指定超时,达到超时之后就会自动放弃获取锁

voidunlock()

解锁Lock实例

锁定的实例应该始终被解锁以避免死锁情况。

锁的推荐使用方式是将锁相关的代码块放在 try/catch 和 finally 块中。

Lock lock = ...;

lock.lock();

try {

// access to the shared resource

} finally {

lock.unlock();

}

除了 Lock 接口之外,java.util.concurrent.lock 包还提供了一个 ReadWriteLock 接口,俗称 「读写锁」,它维护一对锁,一个用于只读操作,一个用于写操作。

对于读写锁,只要没有写入,读锁定可以由多个线程同时保持。

ReadWriteLock 声明了两个方法用于获取读取或写入锁

方法

说明

LockreadLock()

返回一个用于读取的锁

LockwriteLock()

返回一个用于写的锁

锁的实现

ReentrantLock 锁

ReentrantLock 类实现了 Lock 接口。它提供了相同的并发和内存语义,如使用 synchronized 方法和语句访问的隐式监视器锁,而且可以被子类化。

我们写一个范例演示下如何使用 ReenrtantLock 来实现同步

public class SharedObject {

//...

ReentrantLock lock = new ReentrantLock();

int counter = 0;

public void perform() {

lock.lock();

try {

// Critical section here

count++;

} finally {

lock.unlock();

}

}

//...

}

正如上面的示例所示,我们需要确保在 try-finally 块中包装 lock() 和 unlock() 调用以避免死锁情况。

现在,让我们来看看 tryLock() 的工作原理

public void performTryLock(){

//...

boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);

if(isLockAcquired) {

try {

//Critical section here

} finally {

lock.unlock();

}

}

//...

}

上面这个范例中,调用 tryLock() 的线程将等待一秒钟,如果锁定不可用则放弃等待。

ReentrantReadWriteLock

ReentrantReadWriteLock 类实现了 ReadWriteLock 接口。

我们来看一下线程获取 ReadLock 或 WriteLock 的规则:

读锁 : 如果没有线程获得写锁定或请求它,则多个线程可以获取读锁定。

写锁 : 如果没有线程正在读或写,则只有一个线程可以获取写锁。

我们写一个范例演示下如何使用 ReadWriteLock

public class SynchronizedHashMapWithReadWriteLock {

Map 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();

}

}

//...

}

对于这两种 "写" 操作,我们需要使用写锁定来包围临界区,只有一个线程可以访问它

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();

}

}

对于这两种 "读" 操作,我们需要使用读锁定来包围临界区。如果没有正在进行的写操作,多个线程可以访问此部分。

StampedLock

StampedLock 是 Java 8 中引入的。它支持读写锁定。不同的是,锁的获取方法返回的戳记 ( stamp ) 可以用于释放锁定或检查锁定是否仍然有效。

public class StampedLockDemo {

Map 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);

}

}

}

StampedLock 提供的另一个功能是 「 乐观锁 」 。大多数时候,读操作不需要等待写操作完成,因此不需要完全成熟的读锁。相反,我们可以升级到读锁。

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;

}

使用条件

Condition 类让线程能够在执行临界区时等待某些条件发生。当线程获得对临界区的访问但没有执行其操作的必要条件时,可能会发生这种情况。

例如,读线程可以访问共享队列的锁,该队列仍然没有任何数据可供使用。

传统上,Java 为线程互通提供了 wait()、notify() 和 notifyAll() 方法。

Condition 类有类似的机制,而且,还允许我们指定多个条件。

public class ReentrantLockWithCondition {

Stack 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();

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值