显示锁之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之前,在协调对共享对 ...
随机推荐
- 模拟spring工作原理
1.配置文件 Service=service.Impl.ServiceImpl saveDao=dao.daoImpl.saveDaoImpl 2.模拟业务层 --接口 Service package ...
- 14款DevOps/SRE工具,助力提升运维效率
简介 随着平台工程的兴起,DevOps 和 SRE 不断发展,带来了新一代工具,旨在提高软件开发和运维的效率.可扩展性和可靠性. 在本篇文章中,我们将深入探讨一些最具发展前景的工具,它们正在塑造持续集 ...
- 淘宝二面:MySQL里有2000万条数据,但是Redis中只存20万的数据,如何保证redis中的数据都是热点数据?
引言 在当今互联网领域,尤其在大型电商平台如淘宝这样的复杂分布式系统中,数据的高效管理和快速访问至关重要.面对数以千万计的商品.交易记录以及其他各类业务数据,如何在MySQL等传统关系型数据库之外,借 ...
- 报表的 SQL 注入风险是什么意思?如何防范?
啥是 SQL 注入风险? 数据库要执行 SQL 访问数据,数据库是个执行机构,它只会检查传来的 SQL 是不是合乎语法,而并不会关心这个语句是否会造成伤害(数据泄露或破坏).正因为只要符合语法规则就会 ...
- mysql 必知必会整理—数据汇总与分组[七]
前言 简单整理一下数据汇总与分组 正文 我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数.使用这些函数,MySQL查询可用于检索数据,以便分析和报表生成. 这种类型的检索例 ...
- 使用Docker搭建MongoDB 5.0版本副本集集群
1.mongodb集群 首先我们需要了解mongodb的集群模式,mongodb安装分为单机安装和集群安装.集群安装分为:主从复制(Master-Slaver)集群.副本集(Replica Set)集 ...
- Alibaba Cloud Linux 2 LTS 正式发布,提供更高性能和更多保障
Alibaba Cloud Linux 2 LTS版本发布后,阿里云将会为该版本提供长达5年的软件维护.问题修复服务.从2019-03-27开始到2024-03-31结束.包括: 免费的服务和支持:A ...
- 云原生微服务的下一站,微服务引擎 MSE 重磅升级
简介:管好微服务,成为云原生时代的新难题. 管好微服务,成为云原生时代的新难题. 从建好微服务到管好微服务,差的虽是一个字,连接起两边的却需要大量的微服务落地经验.因为软件架构的核心挑战是解决业务快 ...
- DataWorks功能实践速览 05——循环与遍历
简介: DataWorks功能实践系列,帮助您解析业务实现过程中的痛点,提高业务功能使用效率!通过往期的介绍,您已经了解到在DataWorks上进行任务运行的最关键的几个知识点,其中上期参数透传中为 ...
- HTML中元素分类与对应的CSS样式特点
元素就是标签,布局中常用的有三种标签,块元素.内联元素.内联块元素,了解这三种元素的特性,才能熟练的进行页面布局. 块元素 块元素,也可以称为行元素,布局中常用的标签如:div.p.ul.li.h1~ ...