文章目录

  • 写在前面
  • ReentrantLock的重要方法
  • ReentrantLock使用示例
  • ReentrantLock的公平和非公平锁
  • ReentrantLock的重入锁
  • ReentrantLock的Condition机制
  • ReentrantLock与synchronized(内部锁)性能比较
  • Lock与synchronized区别

写在前面

ReentrantLock继承了AbstractQueuedSynchronizer(简称AQS),AQS用到了模板模式这种设计模式,所以阅读AQS和ReentrantLock的源码时,最好对模板设计模式有一定了解,这里我就讲AQS了。

ReentrantLock是除了synchronized用得较多的一种锁。ReentrantLock也属于重入锁,后面接着就会提到它的重入锁实现原理。

ReentrantLock的功能要比内部锁synchronized更多,如指定锁等待时间的方法tryLock(long time,TimeUnit unit)、中断锁的方法lockInterruptibly()、没获取锁直接返回的方法tryLock()。

所以,什么时候选择ReentrantLock呢?

一般是synchronized 不满足需求时,才选择ReentrantLock,比如实现立即返回、可中断、conditon机制时

ReentrantLock的重要方法

方法 说明
lock() 获取锁。如果锁已经被占用,则等待
tryLock() 尝试获取锁,拿到锁返回true,没拿到返回false,并立即返回
tryLock(long time, TimeUnit unit) 在指定时间内会等待获取锁,如果一直拿不到就返回false,并立即返回。在等待过程中可以进行中断
lockInterruptibley() 获取锁。如果线程interrupted了,则跟着抛出异常中断
unLock() 释放锁
newCondition() 创建一个与此 Lock 实例一起使用的 Condition 实例。

ReentrantLock使用示例

 private ReentrantLock lock = new ReentrantLock();public void run() {// 加锁 if(lock.tryLock()){System.out.println(Thread.currentThread().getName()+" 拿到锁");try {Thread.sleep(3000);// doSomething...} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();// 释放锁}} else{System.out.println(Thread.currentThread().getName()+" 获取锁失败");}}

ReentrantLock的公平和非公平锁

公平锁:公平锁讲究先来先到,线程在获取锁时,会先看这个锁的等待队列中是否有线程在等待,如果有,则当前线程就会直接进入等待队列中,而不是直接去抢占锁。

ReentrantLock fairLock = new ReentrantLock(true); // 初始化一个公平锁

非公平锁:不管是否有等待队列,先直接尝试获取锁,如果拿到锁,则立刻占有锁对象;如果未拿到,则自动排到队尾等待。

ReentrantLock fairLock = new ReentrantLock(); // 初始化一个非公平锁
ReentrantLock fairLock = new ReentrantLock(false); // 初始化一个非公平锁

非公平锁的性能比公平锁的性能好很多,所以ReentrantLock默认是非公平锁。

为什么非公平锁性能好?


//公平锁静态内部类
static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// hasQueuedPredecessors返回true,说明当前节点不是头节点或队列不为空,此时直接加在队列后面// hasQueuedPredecessors前面有个非符号,此时不再走&&后面的CAS操作。// hasQueuedPredecessors返回false,说明当前节点是头节点或队列为空,// 说明只有当前线程在竞争锁,此时可以进行compareAndSetState操作。if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}}// 非公平锁静态内部类
static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); // 是Sync的方法}}// Sync的方法
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 重入锁机制int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

看了ReentrantLock的公平锁和非公平锁的源代码你就会发现,公平锁直接走的父类AbstractQueuedSynchronizer的acquire方法,而非公平锁是先作CAS操作。非公平锁这样做的优点是:
1.如果直接拿到了锁,就避免了维护node链表队列
2.如果直接拿到了锁,就避免了线程休眠和唤醒的上下文切换

ReentrantLock的重入锁

ReentrantLock它是怎么实现重入锁的呢?
截取前面小节的部分代码:

     else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}

从代码可以看出,来获取锁的线程如果是当前占有锁的线程,则直接将nextc+1。而且从if (nextc < 0)知道,可重入的次数是int的最大值。刚入门的同学可能不知道为什么会是int的最大的值,这是因为一个int值在做不限制累加,到了最大值2147483647时会溢出变成负数,这个在大学计算机相关课程应该会讲到。

ReentrantLock的Condition机制

ReentrantLock还提供了Condition机制来进行复杂的线程控制功能。

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。

ArrayBlockingQueue的实现就依靠了Condition机制。如下核心代码。

下面代码中Condition机制的核心原理就是:当前线程被哪个condtion阻塞(调用await),就会加到当前condition的阻塞队列里。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;/*** 数组阻塞队列** @param <E>*/
class ArrayBlockingQueueDemon<E> {final ReentrantLock lock;/*** put元素时被阻塞的条件*/final Condition putCondition;/*** take数据时被阻塞的条件*/final Condition takeCondition;/*** 放元素的队列*/final Object[] items;/*** take下一个元素的索引下标*/int takeIndex;/*** put下一个元素的索引下标*/int putIndex;/*** 队列中元素个数*/int count;/*** 构造方法** @param capacity 允许队列* @param fair     是否创建公平锁*/public ArrayBlockingQueueDemon(int capacity, boolean fair) {if (capacity <= 0) {throw new IllegalArgumentException();}this.items = new Object[capacity];lock = new ReentrantLock(fair);takeCondition = lock.newCondition();putCondition = lock.newCondition();}public void put(E e) throws InterruptedException {if (e == null) {throw new NullPointerException();}lock.lockInterruptibly();try {// 队列到达初始化上限时,不再允许向队列放数据,放数据的线程要等待// 此刻,当前线程会被添加到putCondition的阻塞队列里while (count == items.length) {putCondition.await();}// 入队enqueue(e);} finally {lock.unlock();}}public E take() throws InterruptedException {lock.lockInterruptibly();try {// 当队列没有数据时,拿数据的线程等待// 此该,当前线程会被添加到takeCondition的阻塞队列里while (count == 0) {takeCondition.await();}// 出队并返回return dequeue();} finally {lock.unlock();}}private void enqueue(E x) {items[putIndex] = x;++putIndex;if (putIndex == items.length) {putIndex = 0;}count++;// 队列里增加元素了,可以唤醒取元素的线程了takeCondition.signal();}private E dequeue() {E x = (E) items[takeIndex];items[takeIndex] = null;++takeIndex;if (takeIndex == items.length) {takeIndex = 0;}count--;// 队列元素减少了,腾出了位置,可唤醒放元素的线程了putCondition.signal();return x;}}

注意

  1. Condition在使用之前,一定要先获取监视器。即调用Condition的await()和signal()方法的代码,都必须在lock.lock()和lock.unlock之间。

  2. Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。

ReentrantLock与synchronized(内部锁)性能比较

在资源竞争不激烈的情形下,ReentrantLock性能稍微比synchronized差一点。但是当同步非常激烈的时候,synchronized的性能会下降好几十倍。而ReentrantLock不会有太大的性能波动。

在写同步的时候,优先考虑synchronized,毕竟synchronized更简单,总得来说性能被优化得还不错。ReentrantLock比较复杂,写得不好,还可能会给程序性能带来大问题。

Lock与synchronized区别

  1. Lock锁在抛异常时,不会自动释放锁,需要在finally里手动释放。synchronized会手动释放锁

Java锁详解之ReentrantLock相关推荐

  1. Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”

    在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...

  2. 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

    在Java并发场景中,会涉及到各种各样的锁,比如:高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景,这些锁有对应的种类:公平锁,乐观锁,悲观锁等等,这篇文章来详细介绍各种锁的分类: 公 ...

  3. Java锁详解之改进读写锁StampedLock

    文章目录 先了解一下ReentrantReadWriteLock StampedLock重要方法 StampedLock示例 StampedLock可能出现的性能问题 StampedLock原理 St ...

  4. 公平锁非公平锁的实际使用_java 线程公平锁与非公平锁详解及实例代码

    java 线程公平锁与非公平锁详解 在ReentrantLock中很明显可以看到其中同步包括两种,分别是公平的FairSync和非公平的NonfairSync.公平锁的作用就是严格按照线程启动的顺序来 ...

  5. Java 多线程详解(三)------线程的同步

    Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 Java 多线程详解(二 ...

  6. 可重入锁详解(什么是可重入)

    可重入锁详解 概述 什么是 "可重入",可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁.例如 package com.test.reen;// 演示可重入锁是什么 ...

  7. 【运维能力提升计划-3】Java多线程详解

    Java多线程详解 学习链接 Java.Thread 线程简介 线程 进程 多线程 线程实现 Thread 继承Thread类 调用run方法只有主线程一个线程,调用start方法生成子线程与主线程并 ...

  8. Redis分布式锁详解

    Redis分布式锁详解 1. 分布式所概述 1.1 分布式锁 2. 缓存数据库Redis 2.1 redis简介 2.2 Springboot整合Redis两种方式 3. 实现验证 3.1 环境准备 ...

  9. Java多线程详解(线程不安全案例)

    嗨喽-小伙伴们我又来了, 通过前面两章的学习,我们了解了线程的基本概念和创建线程的四种方式. 附上链接: 1.  Java多线程详解(基本概念)​​​​​​​ 2. Java多线程详解(如何创建线程) ...

最新文章

  1. Android系统手机端抓包方法
  2. Matlab与线性代数 -- 矩阵的连接
  3. 如何删除oracle实例
  4. 这个机器人一个表情,看过的人不寒而栗
  5. System Control Processor Firmware简介
  6. php 下载的压缩文件,php在线压缩打包rar并自动下载文件的例子
  7. 修改pip安装源加快python模块安装
  8. 高平二中2021高考成绩查询,录取信息
  9. 嵌入式设备中支持国密算法的方法(三)——移植Openssl库的步骤说明
  10. 专科python应届生工资多少-应届生学Python年薪30万,秘诀是什么?
  11. 面向对象-类属性-类方法---Python
  12. 拉肚子差评回复模板_吃了拉肚子的差评怎么回复?
  13. java 程序员发展
  14. 三级联动下拉框(省市县)存储数据库,包含信息回填
  15. 声律启蒙--喜欢这个韵律
  16. 能不能过?(C++)
  17. UDP 头部结构及协议特点
  18. java画笑脸_canvas画笑脸
  19. IOTOS物联中台从0到1开发modbus_rtu驱动 实例详解
  20. 云借阅-图书管理系统

热门文章

  1. 全局变量/static静态变量在section段中的分布
  2. 两种方法设置html表格的宽高
  3. Golang和Ethereum中的big.Int
  4. pr如何处理音效_Pr基础全通关:从0到1,进阶剪辑大神
  5. const * 和 * const 的区别
  6. (Hook)SetWindowsHookEx和UnhookWindowsHookEx
  7. 中国台湾芯片设计商 Realtek 的WiFi SDK漏洞影响数百万IOT设备
  8. python爬取数据存入mysql
  9. 第一章 Matlab的简单介绍
  10. 【Vue.js】vue用户登录功能