秒懂 Java java.util.concurrent.Locks
admin
2024-05-13 12:06:04
0

对于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 接口提供了哪些方法:

方法说明
void lock()尝试获取锁(如果可用),如果锁不可用,则线程会被阻塞,直到锁被释放
oid lockInterruptibly()类似于 lock(),但它允许被阻塞的线程被中断并通过抛出的 java.lang.InterruptedException 恢复执行
boolean tryLock()lock() 方法的非阻塞版本,它会立即尝试获取锁定,如果锁定成功则返回 true
boolean tryLock(long timeout, TimeUnit timeUnit)类似于 tryLock(),但它可以指定超时,达到超时之后就会自动放弃获取锁
void unlock()解锁 Lock 实例

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

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

Lock lock = ...; 
lock.lock();
try {// access to the shared resource
} finally {lock.unlock();
}

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

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

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

方法说明
Lock readLock()返回一个用于读取的锁
Lock writeLock()返回一个用于写的锁

锁的实现

ReentrantLock 锁

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

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

public class SharedObject {//...ReentrantLock lock = new ReentrantLock();int counter = 0;public void perform() {lock.lock();try {// Critical section herecount++;} 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();}}
}

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...