ReentrantLock显示锁

在Java 1.5之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile两种。Java1.5增加了一种新的机制:ReentrantLock。但ReentrantLock并不是替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。在学习显示锁之前,我们需要明确两个问题:

  1. 什么是可重入锁
  2. 锁的公平性指的是什么

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. 什么时候使用显示锁

可以看到,显示锁可以完成内置锁能完成的所有工作,但它不仅限于此。

  1. 它提供了可中断阻塞机制,在synchronized内置锁中,线程一旦因为未获取到锁而阻塞则不可被中断,而在显示锁中提供了可中断等待锁的机制。
  2. 显示锁提供了时间约束的锁等待机制。
  3. 显示锁提供了公平锁的实现(显示锁和内置锁默认都是非公平锁)
  4. 显示锁通过Condition实现了更丰富的线程协作机制(例如在内置锁中wait方法是可中断的,而condition可以提供不可中断的等待)
  5. 就性能来说显示锁和内置锁无明显差异
  6. 内置锁书写更简洁,在无以上特殊需求的情况下,优先使用内置锁。

参考文章

Java之显示锁

究竟什么是可重入锁?

公平锁与非公平锁+效率差异原因

Java并发之Condition

显示锁之ReentrantLock的更多相关文章

  1. Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock

    在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...

  2. 多线程并发编程之显示锁ReentrantLock和读写锁

    在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是 ...

  3. 显示锁ReentrantLock和Condition的使用

    一.ReentrantLock (1).java.util.concurrent.locks包中的ReentrantLock就是重入锁,它实现了Lock接口,Lock加锁和解锁都是显示的.Reentr ...

  4. Java并发——显示锁

    Java提供一系列的显示锁类,均位于java.util.concurrent.locks包中. 锁的分类: 排他锁,共享锁 排他锁又被称为独占锁,即读写互斥.写写互斥.读读互斥. Java的ReadW ...

  5. JAVA并发-基于AQS实现自己的显示锁

    一.了解什么是AQS 原文链接:http://www.studyshare.cn/blog-front/blog/details/1131 AQS是AbstractQueuedSynchronizer ...

  6. 多线程安全问题之Lock显示锁

    package com.hls.juc; import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.Reentr ...

  7. 显式锁(二)Lock接口与显示锁介绍

    一.显式锁简介    显式锁,这个叫法是相对于隐式锁synchronized而言的,加锁和解锁都要用户显式地控制.显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一 ...

  8. Java多线程之内置锁与显示锁

    Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结. Synchronized 内置锁获得锁和释放 ...

  9. 6.显示锁Lock 和 线程通信Condition

    显示锁 Lock 一.用于解决多线程 安全问题的方式: synchronized:   1.同步代码块      2.同步方法 jdk1.5 后:第三种:同步锁Lock  (注意:同步(synchro ...

  10. Java 显示锁 之 队列同步器AQS(六)

    1.简述 锁时用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源.但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁. 在Java 5.0之前,在协调对共享对 ...

随机推荐

  1. MogDB/openGauss关于PL/SQL匿名块调用测试

    MogDB/openGauss 关于 PL/SQL 匿名块调用测试 一.原理介绍 PL/SQL(Procedure Language/Structure Query Language)是标准 SQL ...

  2. Unity 音频资源优化

    1.声道设置 (1).不设置 单声道 音频大小为下图 (2).设置单声道 音频大小为下图 2.加载类型 (1).Decompress On Load 使用内存8.1M (2).Compressed I ...

  3. HarmonyOS后台任务管理开发指南上线!

      为什么要使用后台任务?开发过程中如何选择合适的后台任务?后台任务申请时存在哪些约束与限制? 针对开发者使用后台任务中的疑问,我们上线了概念更明确.逻辑结构更清晰的后台任务开发指南,包含具体的使用场 ...

  4. 码力全开!请查收HDC.Together 2023亮点日程

    <主题演讲> <技术交流与互动> <妙趣之旅>

  5. Spring Cloud Bus:消息总线

    Spring Cloud Bus:消息总线 SpringCloud学习教程 SpringCloud Spring Cloud Bus 使用轻量级的消息代理来连接微服务架构中的各个服务,可以将其用于广播 ...

  6. 扩展中国剩余定理证明及例题 Strange Way to Express Integers

    前置知识 中国剩余定理(CRT),逆元: EXCRT是什么 我们知道,对于 对于 \[\begin{equation} \begin{cases} x \equiv c_1 \ (mod \ m_1) ...

  7. javascript现代编程系列教程之六——数字型数据类型转换

    一.整数转换 在 JavaScript 中,parseInt() 函数会将其参数转换为字符串,然后解析该字符串,并返回一个整数或 NaN.如果 parseInt() 函数的参数是一个非常大的浮点数(如 ...

  8. 【c++】类valarray介绍

    valarray类用于处理数组中的数值,如将所有元素相加,找出最大.最小值,数组长度. 如何使用valarray类: 1.首先需要声明头文件        #include<valarray&g ...

  9. 阿里云2020上云采购季,你的ECS买好了吗?

    阿里云2020上云采购季,超级品类日,天天有爆款. 今日爆款推荐:云服务器. 重磅推荐两款,限时抢购. 新品共享型s6: 企业级共享型n4: 想看更多云产品,来阿里云采购季: https://www. ...

  10. 阿里云峰会 | 阿里云CDN六大边缘安全能力,全力助推政企数字化转型

    6月9日,2020年阿里云线上峰会召开.阿里云智能总裁张建锋认为,数字化已经成为中国经济的主要驱动力,疫情让政府.企业都认识到数字化的迫切性.在峰会上,阿里云CDN正式对外发布基于CDN构建的六大边缘 ...