显示锁之ReentrantLock
ReentrantLock显示锁
在Java 1.5之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile两种。Java1.5增加了一种新的机制:ReentrantLock。但ReentrantLock并不是替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。在学习显示锁之前,我们需要明确两个问题:
- 什么是可重入锁
- 锁的公平性指的是什么
1. 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。
public class Test {
public synchronized void A() {
B();
}
public synchronized void B() { }
}
由于synchronized提供的是可重入锁,所以在调用A方法获取到锁后,再进入B方法时仍可以获取到锁,不会发生死锁的问题。
2. 公平锁与非公平锁
2.1 公平锁与非公平锁的定义
公平锁指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。
而非公平锁就是调度系统并不保证按照线程申请锁的顺序来交付锁,如果某个时刻锁空闲,而刚好有一个线程来申请锁,则将锁交给该线程,也就不会唤醒已经挂起的锁。
2.2 公平锁与非公平锁的效率问题
大多数情况下,非公平锁的效率是高于公平锁的。这是因为公平锁在有新的线程请求锁时仍将其加到队列的末尾,该线程会被挂起,然后唤醒队列头部的线程获取锁;但如果是非公平锁,此时有一个线程请求锁,恰好此时锁没有被占用,此时调度系统会将锁交付给这个新线程,而不会死板的将新线程加入队尾并挂起,然后唤醒队头线程。也就是说非公平锁会降低线程被挂起的几率,而恢复一个被挂起的线程与该线程真正运行之间存在严重的延迟。
了解完这两个概念后,我们来学习显示锁:
3. Lock
lock是一个接口,定义了一组抽象的加锁操作。与synchronized不同的是,lock提供了一种无条件的、可轮询的、定时的以及可中断的锁的获取操作。
public interface Lock {
//获得锁,如果锁定不可用,则当前线程将被禁用以进行线程调度,并且在此之前处于休眠状态。
void lock();
//如果当前线程未被中断,则获取锁。如果锁可用,则获取锁,并立即返回。可中断锁获取操作的实现
void lockInterruptibly() throws InterruptedException;
//仅在调用时锁为空闲状态才获取该锁。通常对于那些不是必须获取锁的操作可能有用。轮询锁及可定时锁获取操作的实现。
boolean tryLock();
//在指定时间内获取锁,时间单位由第二参数决定
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁。对应于lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的话应该对应着一个unlock(),这样可以避免死锁或者资源浪费。
void unlock();
//返回绑定到此 Lock 实例的新 Condition 实例。
Condition newCondition();
}
4.ReentrantLock
ReentrantLock是Lock接口的实现类,它提供了与synchronized相同的互斥性、内存可见性及可重入的加锁语义,在加锁与释放锁时也与synchronized有相同的内存语义。
4.1 ReentrantLock的公平性问题
ReentrantLock默认是非公平锁(synchronized也是非公平锁),也就是说。但是我们可以通过重载构造方法来创建公平锁:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//如果为true则创建公平锁,false创建非公平锁
}
4.2 ReentrantLock可重入性
ReentrantLock是可重入锁。
4.3 显示锁的基本使用
public class LockDemo {
private Lock lock = new ReentrantLock();//创建显示锁
private int count;
public void increament() {
lock.lock();//加锁
try {
count++;
}finally {
lock.unlock();//解锁一定要在finally中执行
}
}
}
需要注意的是解锁一定要在finally中进行,这是JDK注释给出的规范。
4.4 Condition实现线程协作
在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。Object和Condition接口的一些对比。

在显示锁中我们需要通过Condition来进行wait和notify操作,下面是Condition接口:
public interface Condition {
void await() throws InterruptedException;//对应Object中的wait方法,使获取锁的当前线程等待
void awaitUninterruptibly();//不可中断等待
long awaitNanos(long nanosTimeout) throws InterruptedException;//等待指定时间,以纳秒为单位
boolean await(long time, TimeUnit unit) throws InterruptedException;//等待指定时间,时间单位由第二个参数指定
boolean awaitUntil(Date deadline) throws InterruptedException;//造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
void signal();//唤醒等待队列中的一个线程,相当于notify。
void signalAll();//唤醒当前等待队列中所有线程,相当于notifuyAll。
}
需要注意的是在synchronized内置锁中,只会维持一个等待队列,而在显示锁中可以通过多次执行newCondition()方法获取多个等待队列,如果让每个线程对应一个condition,那么就可以实现线程的精准唤醒。
我们就拿生产者消费者示例代码来看看,如何使用多个condition实现线程的精准唤醒:
public class SharedResource {
private int empty = 1;//代表缓存池中的空位,如果是1代表还有一个空位,此时需要一个生产者来生产产品方进去
private String buffer;
private Lock lock = new ReentrantLock(true);//创建非公平锁
private Condition producterCondition = lock.newCondition();//生产者condition,专门用于生产者的等待与唤醒
private Condition consumerCondition = lock.newCondition();//消费者cndition
public void setbuffer(String str) {
lock.lock();
while (empty == 0) {
try {
producterCondition.await();//让生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer = str;
empty = 0;
System.out.println(Thread.currentThread().getName() + "生产:" + str);
try {
consumerCondition.signalAll();//精确唤醒消费者
} finally {
lock.unlock();
}
}
public void getBuffer() {
while (empty == 1) {//如果缓存池中有一个空位,也就表示没有产品在缓存池中
try {
consumerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String temp = buffer;
buffer = null;
empty = 1;
System.out.println(Thread.currentThread().getName() + "消费:" + temp);
try {
producterCondition.signalAll();//精确唤醒生产者
} finally {
lock.unlock();
}
}
}
5. 什么时候使用显示锁
可以看到,显示锁可以完成内置锁能完成的所有工作,但它不仅限于此。
- 它提供了可中断阻塞机制,在synchronized内置锁中,线程一旦因为未获取到锁而阻塞则不可被中断,而在显示锁中提供了可中断等待锁的机制。
- 显示锁提供了时间约束的锁等待机制。
- 显示锁提供了公平锁的实现(显示锁和内置锁默认都是非公平锁)
- 显示锁通过Condition实现了更丰富的线程协作机制(例如在内置锁中wait方法是可中断的,而condition可以提供不可中断的等待)
- 就性能来说显示锁和内置锁无明显差异
- 内置锁书写更简洁,在无以上特殊需求的情况下,优先使用内置锁。
参考文章:
显示锁之ReentrantLock的更多相关文章
- Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock
在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...
- 多线程并发编程之显示锁ReentrantLock和读写锁
在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是 ...
- 显示锁ReentrantLock和Condition的使用
一.ReentrantLock (1).java.util.concurrent.locks包中的ReentrantLock就是重入锁,它实现了Lock接口,Lock加锁和解锁都是显示的.Reentr ...
- Java并发——显示锁
Java提供一系列的显示锁类,均位于java.util.concurrent.locks包中. 锁的分类: 排他锁,共享锁 排他锁又被称为独占锁,即读写互斥.写写互斥.读读互斥. Java的ReadW ...
- JAVA并发-基于AQS实现自己的显示锁
一.了解什么是AQS 原文链接:http://www.studyshare.cn/blog-front/blog/details/1131 AQS是AbstractQueuedSynchronizer ...
- 多线程安全问题之Lock显示锁
package com.hls.juc; import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.Reentr ...
- 显式锁(二)Lock接口与显示锁介绍
一.显式锁简介 显式锁,这个叫法是相对于隐式锁synchronized而言的,加锁和解锁都要用户显式地控制.显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一 ...
- Java多线程之内置锁与显示锁
Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结. Synchronized 内置锁获得锁和释放 ...
- 6.显示锁Lock 和 线程通信Condition
显示锁 Lock 一.用于解决多线程 安全问题的方式: synchronized: 1.同步代码块 2.同步方法 jdk1.5 后:第三种:同步锁Lock (注意:同步(synchro ...
- Java 显示锁 之 队列同步器AQS(六)
1.简述 锁时用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源.但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁. 在Java 5.0之前,在协调对共享对 ...
随机推荐
- Ubuntu部署Django三:编写相关配置文件及启动服务
1. uwsgi 1.1 项目结构如下,你要知道 uwsgi.ini 放在什么位置 projectName |-- app |-- projectName |-- -- wsgi.py |-- -- ...
- Linux之识别HBA的WWN
一.概念 FC HBA,也即Fibre Channel Host Bus Adapter,光纤通道主机适配器,简称光纤适配器. 在FC网络环境中,主机需要和FC网络.FC存储设备(SAN磁盘阵列)连接 ...
- Perm 排列计数——Lucas&dfs
思路:这道题给出的公式看明白后即可得出正解,我们可以把他想象成一颗二叉树,任意一个点的任意一个子孙一直除以2后最终都会到达一终点,终点则为以该点为根的子树的最小值. so--我们可以将根节点作为最后终 ...
- k8s 深入篇———— Job与CronJob[十]
开篇 简要演练一下job 和 cronjob 正文 实际上,它们主要编排的对象,都是"在线业务",即:Long Running Task(长作业).比如,我在前面举例时常用的 Ng ...
- 微信小程序三种授权登录的方式
经过一段时间对微信小程序的研发后 总结出以下三种授权登录的方式,我给他们命名为'一次性授权''永久授权''不授权' 1.一次性授权常规写法,需要获取用户公开信息(头像,昵称等)时,判断调取授权登录接口 ...
- MUI-拿到版本名称和版本code
场景: 一般App发布了新版本之后,都会提示用户去更新.这就需要我们获取App的当前版本号与后台存储的版本号做对比来判断是否需要更新. 获取版本名称和版本code: 打开manifest.json文件 ...
- 使用ollama + AnythingLLM快速且简单的在本地部署llm3
使用ollama + AnythingLLM快速且简单的在本地部署llm3 不多说,直接开始 一.安装ollama ollama官网:https://ollama.com/ 下载地址:https:// ...
- 【USENIX ATC】支持异构GPU集群的超大规模模型的高效的分布式训练框架Whale
简介: 高效大模型训练框架Whale(EPL)入选USENIX ATC 作者:张杰.贾贤艳 近日,阿里云机器学习PAI关于深度学习模型高效的分布式训练框架的论文< Whale: Efficien ...
- 淘宝推荐、视频搜索背后的检索技术竟是它!深度揭秘达摩院向量检索引擎Proxima
简介: 淘宝搜索推荐.视频搜索的背后使用了什么样的检索技术?非结构化数据检索,向量检索,以及多模态检索,它们到底解决了什么问题?今天由阿里巴巴达摩院的科学家从业务问题出发,抽丝剥茧,深度揭秘达摩院内部 ...
- 使用 Arthas 排查 SpringBoot 诡异耗时的 Bug
简介: 公司有个渠道系统,专门对接三方渠道使用,没有什么业务逻辑,主要是转换报文和参数校验之类的工作,起着一个承上启下的作用.最近,在优化接口的响应时间,优化了代码之后,但是时间还是达不到要求:有一个 ...