Java锁详解之ReentrantLock
文章目录
- 写在前面
- 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;}}
注意:
Condition在使用之前,一定要先获取监视器。即调用Condition的await()和signal()方法的代码,都必须在lock.lock()和lock.unlock之间。
Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。
ReentrantLock与synchronized(内部锁)性能比较
在资源竞争不激烈的情形下,ReentrantLock性能稍微比synchronized差一点。但是当同步非常激烈的时候,synchronized的性能会下降好几十倍。而ReentrantLock不会有太大的性能波动。
在写同步的时候,优先考虑synchronized,毕竟synchronized更简单,总得来说性能被优化得还不错。ReentrantLock比较复杂,写得不好,还可能会给程序性能带来大问题。
Lock与synchronized区别
- Lock锁在抛异常时,不会自动释放锁,需要在finally里手动释放。synchronized会手动释放锁
Java锁详解之ReentrantLock相关推荐
- Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”
在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...
- 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁
在Java并发场景中,会涉及到各种各样的锁,比如:高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景,这些锁有对应的种类:公平锁,乐观锁,悲观锁等等,这篇文章来详细介绍各种锁的分类: 公 ...
- Java锁详解之改进读写锁StampedLock
文章目录 先了解一下ReentrantReadWriteLock StampedLock重要方法 StampedLock示例 StampedLock可能出现的性能问题 StampedLock原理 St ...
- 公平锁非公平锁的实际使用_java 线程公平锁与非公平锁详解及实例代码
java 线程公平锁与非公平锁详解 在ReentrantLock中很明显可以看到其中同步包括两种,分别是公平的FairSync和非公平的NonfairSync.公平锁的作用就是严格按照线程启动的顺序来 ...
- Java 多线程详解(三)------线程的同步
Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 Java 多线程详解(二 ...
- 可重入锁详解(什么是可重入)
可重入锁详解 概述 什么是 "可重入",可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁.例如 package com.test.reen;// 演示可重入锁是什么 ...
- 【运维能力提升计划-3】Java多线程详解
Java多线程详解 学习链接 Java.Thread 线程简介 线程 进程 多线程 线程实现 Thread 继承Thread类 调用run方法只有主线程一个线程,调用start方法生成子线程与主线程并 ...
- Redis分布式锁详解
Redis分布式锁详解 1. 分布式所概述 1.1 分布式锁 2. 缓存数据库Redis 2.1 redis简介 2.2 Springboot整合Redis两种方式 3. 实现验证 3.1 环境准备 ...
- Java多线程详解(线程不安全案例)
嗨喽-小伙伴们我又来了, 通过前面两章的学习,我们了解了线程的基本概念和创建线程的四种方式. 附上链接: 1. Java多线程详解(基本概念) 2. Java多线程详解(如何创建线程) ...
最新文章
- Android系统手机端抓包方法
- Matlab与线性代数 -- 矩阵的连接
- 如何删除oracle实例
- 这个机器人一个表情,看过的人不寒而栗
- System Control Processor Firmware简介
- php 下载的压缩文件,php在线压缩打包rar并自动下载文件的例子
- 修改pip安装源加快python模块安装
- 高平二中2021高考成绩查询,录取信息
- 嵌入式设备中支持国密算法的方法(三)——移植Openssl库的步骤说明
- 专科python应届生工资多少-应届生学Python年薪30万,秘诀是什么?
- 面向对象-类属性-类方法---Python
- 拉肚子差评回复模板_吃了拉肚子的差评怎么回复?
- java 程序员发展
- 三级联动下拉框(省市县)存储数据库,包含信息回填
- 声律启蒙--喜欢这个韵律
- 能不能过?(C++)
- UDP 头部结构及协议特点
- java画笑脸_canvas画笑脸
- IOTOS物联中台从0到1开发modbus_rtu驱动 实例详解
- 云借阅-图书管理系统