Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
沉默王二
Jmx Java
提交
02ce418b
J
Jmx Java
项目概览
沉默王二
/
Jmx Java
大约 1 年 前同步成功
通知
160
Star
18
Fork
2
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
Jmx Java
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
02ce418b
编写于
8月 11, 2023
作者:
沉默王二
💬
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
读写锁
上级
d8c183dd
变更
9
隐藏空白更改
内联
并排
Showing
9 changed file
with
169 addition
and
61 deletion
+169
-61
README.md
README.md
+2
-2
docs/.vuepress/sidebar.ts
docs/.vuepress/sidebar.ts
+1
-1
docs/home.md
docs/home.md
+1
-1
docs/thread/ReentrantReadWriteLock.md
docs/thread/ReentrantReadWriteLock.md
+23
-30
docs/thread/aqs.md
docs/thread/aqs.md
+1
-1
docs/thread/cas.md
docs/thread/cas.md
+1
-1
docs/thread/lock.md
docs/thread/lock.md
+1
-1
docs/thread/pianxiangsuo.md
docs/thread/pianxiangsuo.md
+1
-1
docs/thread/reentrantLock.md
docs/thread/reentrantLock.md
+138
-23
未找到文件。
README.md
浏览文件 @
02ce418b
...
...
@@ -253,12 +253,12 @@
-
[
volatile关键字解析
](
docs/thread/volatile.md
)
-
[
synchronized关键字解析
](
docs/thread/synchronized-1.md
)
-
[
synchronized锁的到底是什么?
](
docs/thread/synchronized.md
)
-
[
JDK15 移除了偏向锁
](
docs/thread/pianxiangsuo.md
)
-
[
CAS详解
](
docs/thread/cas.md
)
-
[
AQS详解
](
docs/thread/aqs.md
)
-
[
JUC 包下的那些锁
](
docs/thread/lock.md
)
-
[
JDK15 移除了偏向锁
](
docs/thread/pianxiangsuo.md
)
-
[
重入锁ReentrantLock
](
docs/thread/reentrantLock.md
)
-
[
深入理解Java并发
读写锁ReentrantReadWriteLock
](
docs/thread/ReentrantReadWriteLock.md
)
-
[
读写锁ReentrantReadWriteLock
](
docs/thread/ReentrantReadWriteLock.md
)
-
[
深入理解Java并发线程协作类Condition
](
docs/thread/condition.md
)
-
[
深入理解Java并发线程线程阻塞唤醒类LockSupport
](
docs/thread/LockSupport.md
)
-
[
简单聊聊Java的并发集合容器
](
docs/thread/map.md
)
...
...
docs/.vuepress/sidebar.ts
浏览文件 @
02ce418b
...
...
@@ -291,10 +291,10 @@ export const sidebarConfig = sidebar({
"
volatile
"
,
"
synchronized-1
"
,
"
synchronized
"
,
"
pianxiangsuo
"
,
"
cas
"
,
"
aqs
"
,
"
lock
"
,
"
pianxiangsuo
"
,
"
reentrantLock
"
,
"
ReentrantReadWriteLock
"
,
"
condition
"
,
...
...
docs/home.md
浏览文件 @
02ce418b
...
...
@@ -262,10 +262,10 @@ head:
-
[
volatile关键字解析
](
thread/volatile.md
)
-
[
synchronized关键字解析
](
thread/synchronized-1.md
)
-
[
synchronized锁的到底是什么?
](
thread/synchronized.md
)
-
[
JDK15 移除了偏向锁
](
thread/pianxiangsuo.md
)
-
[
CAS详解
](
thread/cas.md
)
-
[
AQS详解
](
thread/aqs.md
)
-
[
JUC 包下的那些锁
](
thread/lock.md
)
-
[
JDK15 移除了偏向锁
](
thread/pianxiangsuo.md
)
-
[
重入锁ReentrantLock
](
thread/reentrantLock.md
)
-
[
深入理解Java并发读写锁ReentrantReadWriteLock
](
thread/ReentrantReadWriteLock.md
)
-
[
深入理解Java并发线程协作类Condition
](
thread/condition.md
)
...
...
docs/thread/ReentrantReadWriteLock.md
浏览文件 @
02ce418b
...
...
@@ -12,13 +12,13 @@ head:
content
:
Java,并发编程,多线程,Thread,读写锁,ReentrantReadWriteLock
---
#
深入理解Java并发读写锁
ReentrantReadWriteLock
#
14.16 读写锁
ReentrantReadWriteLock
在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用
java提供的关键字synchronized或者concurrents包中实现了Lock接口的
ReentrantLock。它们都是独占式获取锁,也就是在同一时刻只有一个线程能够获取锁。
在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用
java 提供的关键字 synchronized 或者 concurrents 包中实现了 Lock 接口的
ReentrantLock。它们都是独占式获取锁,也就是在同一时刻只有一个线程能够获取锁。
而在一些业务场景中,大部分只是读数据,写数据很少,如果仅仅是读数据的话并不会影响数据正确性(出现脏读),而如果在这种业务场景下,依然使用独占锁的话,很显然这将是出现性能瓶颈的地方。针对这种读多写少的情况,java
还提供了另外一个实现Lock接口的
ReentrantReadWriteLock(读写锁)。
而在一些业务场景中,大部分只是读数据,写数据很少,如果仅仅是读数据的话并不会影响数据正确性(出现脏读),而如果在这种业务场景下,依然使用独占锁的话,很显然这将是出现性能瓶颈的地方。针对这种读多写少的情况,java
还提供了另外一个实现 Lock 接口的
ReentrantReadWriteLock(读写锁)。
**读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞**
。在分析
WirteLock和ReadLock的互斥性时可以按照WriteLock与WriteLock之间,WriteLock与ReadLock之间以及ReadLock与ReadLock
之间进行分析。
**读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞**
。在分析
WirteLock 和 ReadLock 的互斥性时可以按照 WriteLock 与 WriteLock 之间,WriteLock 与 ReadLock 之间以及 ReadLock 与 ReadLock
之间进行分析。
更多关于读写锁特性介绍大家可以看源码上的介绍(阅读源码时最好的一种学习方式,我也正在学习中,与大家共勉),这里做一个归纳总结:
...
...
@@ -37,7 +37,8 @@ head:
## 写锁详解
### 写锁的获取
同步组件的实现聚合了同步器(AQS),并通过重写重写同步器(AQS)中的方法实现同步组件的同步语义。因此,写锁的实现依然也是采用这种方式。在同一时刻写锁是不能被多个线程所获取,很显然写锁是独占式锁,而实现写锁的同步语义是通过重写AQS中的tryAcquire方法实现的。源码为:
同步组件的实现聚合了同步器(AQS),并通过重写重写同步器(AQS)中的方法实现同步组件的同步语义。因此,写锁的实现依然也是采用这种方式。在同一时刻写锁是不能被多个线程所获取,很显然写锁是独占式锁,而实现写锁的同步语义是通过重写 AQS 中的 tryAcquire 方法实现的。源码为:
```
java
protected
final
boolean
tryAcquire
(
int
acquires
)
{
...
...
@@ -78,27 +79,23 @@ protected final boolean tryAcquire(int acquires) {
return
true
;
}
```
这段代码的逻辑请看注释,这里有一个地方需要重点关注,exclusiveCount(c)方法,该方法源码为:
`static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }`
其中
**EXCLUSIVE_MASK**
为:
`static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;`
EXCLUSIVE_MASK 为1左移16位然后减1,即为0x0000FFFF。而exclusiveCount方法是将同步状态(state为int类型)与0x0000FFFF相与,即取同步状态的低16位。那么低16位代表什么呢?根据exclusiveCount方法的注释为独占式获取的次数即写锁被获取的次数,现在就可以得出来一个结论
**同步状态的低16位用来表示写锁的获取次数**
。同时还有一个方法值得我们注意:
其中
**EXCLUSIVE_MASK**
为:
`static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;`
EXCLUSIVE_MASK 为 1 左移 16 位然后减 1,即为 0x0000FFFF。而 exclusiveCount 方法是将同步状态(state 为 int 类型)与 0x0000FFFF 相与,即取同步状态的低 16 位。那么低 16 位代表什么呢?根据 exclusiveCount 方法的注释为独占式获取的次数即写锁被获取的次数,现在就可以得出来一个结论
**同步状态的低 16 位用来表示写锁的获取次数**
。同时还有一个方法值得我们注意:
`static int sharedCount(int c) { return c >>> SHARED_SHIFT; }`
该方法是获取读锁被获取的次数,是将同步状态(int c)右移
16次,即取同步状态的高16位,现在我们可以得出另外一个结论
**同步状态的高16
位用来表示读锁被获取的次数**
。现在还记得我们开篇说的需要弄懂的第一个问题吗?读写锁是怎样实现分别记录读锁和写锁的状态的,现在这个问题的答案就已经被我们弄清楚了,其示意图如下图所示:
该方法是获取读锁被获取的次数,是将同步状态(int c)右移
16 次,即取同步状态的高 16 位,现在我们可以得出另外一个结论
**同步状态的高 16
位用来表示读锁被获取的次数**
。现在还记得我们开篇说的需要弄懂的第一个问题吗?读写锁是怎样实现分别记录读锁和写锁的状态的,现在这个问题的答案就已经被我们弄清楚了,其示意图如下图所示:
![
读写锁的读写状态设计
](
https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/ReentrantReadWriteLock-f714bdd6-917a-4d25-ac11-7e85b0ec1b14.png
)
现在我们回过头来看写锁获取方法tryAcquire,其主要逻辑为:
**当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。**
现在我们回过头来看写锁获取方法 tryAcquire,其主要逻辑为:
**当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。**
### 写锁的释放
写锁释放通过重写
AQS的tryRelease
方法,源码为:
写锁释放通过重写
AQS 的 tryRelease
方法,源码为:
```
java
protected
final
boolean
tryRelease
(
int
releases
)
{
...
...
@@ -115,15 +112,14 @@ protected final boolean tryRelease(int releases) {
return
free
;
}
```
源码的实现逻辑请看注释,不难理解与
ReentrantLock基本一致,这里需要注意的是,减少写状态
` int nextc = getState() - releases;`
只需要用
**当前同步状态直接减去写状态的原因正是我们刚才所说的写状态是由同步状态的低16
位表示的**
。
源码的实现逻辑请看注释,不难理解与
ReentrantLock 基本一致,这里需要注意的是,减少写状态
` int nextc = getState() - releases;`
只需要用
**当前同步状态直接减去写状态的原因正是我们刚才所说的写状态是由同步状态的低 16
位表示的**
。
## 读锁详解
### 读锁的获取
看完了写锁,现在来看看读锁,读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。按照之前对
AQS介绍,实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared
方法。读锁的获取实现方法为:
看完了写锁,现在来看看读锁,读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。按照之前对
AQS 介绍,实现共享式同步组件的同步语义需要通过重写 AQS 的 tryAcquireShared 方法和 tryReleaseShared
方法。读锁的获取实现方法为:
```
java
protected
final
int
tryAcquireShared
(
int
unused
)
{
...
...
@@ -175,15 +171,14 @@ protected final int tryAcquireShared(int unused) {
return
fullTryAcquireShared
(
current
);
}
```
代码的逻辑请看注释,需要注意的是
**当写锁被其他线程获取后,读锁获取失败**
,否则获取成功利用CAS
更新同步状态。
代码的逻辑请看注释,需要注意的是
**当写锁被其他线程获取后,读锁获取失败**
,否则获取成功利用 CAS
更新同步状态。
另外,当前同步状态需要加上
SHARED_UNIT(
`(1 << SHARED_SHIFT)`
即0x00010000)的原因这是我们在上面所说的同步状态的高16位用来表示读锁被获取的次数。如果CAS失败或者已经获取读锁的线程再次获取读锁时,是靠fullTryAcquireShared
方法实现的,这段代码就不展开说了,有兴趣可以看看。
另外,当前同步状态需要加上
SHARED_UNIT(
`(1 << SHARED_SHIFT)`
即 0x00010000)的原因这是我们在上面所说的同步状态的高 16 位用来表示读锁被获取的次数。如果 CAS 失败或者已经获取读锁的线程再次获取读锁时,是靠 fullTryAcquireShared
方法实现的,这段代码就不展开说了,有兴趣可以看看。
### 读锁的释放
读锁释放的实现主要通过方法tryReleaseShared,源码如下,主要逻辑请看注释:
读锁释放的实现主要通过方法
tryReleaseShared,源码如下,主要逻辑请看注释:
```
java
protected
final
boolean
tryReleaseShared
(
int
unused
)
{
...
...
@@ -219,11 +214,10 @@ protected final boolean tryReleaseShared(int unused) {
}
}
```
## 锁降级
读写锁支持锁降级,
**遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁**
,不支持锁升级,关于锁降级下面的示例代码摘自
ReentrantWriteReadLock
源码中:
读写锁支持锁降级,
**遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁**
,不支持锁升级,关于锁降级下面的示例代码摘自
ReentrantWriteReadLock
源码中:
```
java
void
processCachedData
()
{
...
...
@@ -253,19 +247,18 @@ void processCachedData() {
}
}
```
---
>编辑:沉默王二,内容大部分来源以下三个开源仓库:
>- [深入浅出 Java 多线程](http://concurrent.redspider.group/)
>- [并发编程知识总结](https://github.com/CL0610/Java-concurrency)
>- [Java八股文](https://github.com/CoderLeixiaoshuai/java-eight-part)
> 编辑:沉默王二,内容大部分来源以下三个开源仓库:
>
> - [深入浅出 Java 多线程](http://concurrent.redspider.group/)
> - [并发编程知识总结](https://github.com/CL0610/Java-concurrency)
> - [Java 八股文](https://github.com/CoderLeixiaoshuai/java-eight-part)
----
GitHub 上标星 8700+ 的开源知识库《
[
二哥的 Java 进阶之路
](
https://github.com/itwanger/toBeBetterJavaer
)
》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:
[
太赞了,GitHub 上标星 8700+ 的 Java 教程
](
https://javabetter.cn/overview/
)
---
GitHub 上标星 8700+ 的开源知识库《
[
二哥的 Java 进阶之路
](
https://github.com/itwanger/toBeBetterJavaer
)
》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:
[
太赞了,GitHub 上标星 8700+ 的 Java 教程
](
https://javabetter.cn/overview/
)
微信搜
**沉默王二**
或扫描下方二维码关注二哥的原创公众号沉默王二,回复
**222**
即可免费领取。
...
...
docs/thread/aqs.md
浏览文件 @
02ce418b
...
...
@@ -12,7 +12,7 @@ head:
content
:
Java,并发编程,多线程,Thread,AQS
---
# 14.1
2
抽象队列同步器 AQS
# 14.1
3
抽象队列同步器 AQS
**AQS**
是
`AbstractQueuedSynchronizer`
的简称,即
`抽象队列同步器`
,从字面意思上理解:
...
...
docs/thread/cas.md
浏览文件 @
02ce418b
...
...
@@ -12,7 +12,7 @@ head:
content
:
Java,并发编程,多线程,Thread,cas
---
# 14.1
1
乐观锁 CAS
# 14.1
2
乐观锁 CAS
在并发编程中,我们都知道
`i++`
操作是非线程安全的,这是因为
`i++`
操作不是原子操作,我们之前在讲
[
多线程带来了什么问题
](
https://javabetter.cn/thread/thread-bring-some-problem.html
)
中有讲到,大家应该还记得吧?
...
...
docs/thread/lock.md
浏览文件 @
02ce418b
...
...
@@ -12,7 +12,7 @@ head:
content
:
Java,并发编程,多线程,Thread,锁
---
# 14.1
3
JUC 包下的那些锁
# 14.1
4
JUC 包下的那些锁
前面我们介绍了 Java 原生的锁——基于对象的锁,它一般是配合
[
synchronized 关键字
](
https://javabetter.cn/thread/synchronized-1.html
)
来使用的。实际上,Java 在
`java.util.concurrent.locks`
包下,还为我们提供了几个关于锁的类和接口。它们有更强大的功能或更高的性能。
...
...
docs/thread/pianxiangsuo.md
浏览文件 @
02ce418b
...
...
@@ -12,7 +12,7 @@ head:
content
:
Java,并发编程,多线程,Thread,偏向锁
---
# 14.1
4
JDK15 移除了偏向锁
# 14.1
1
JDK15 移除了偏向锁
在 JDK 1.5 之前,面对 Java 并发问题,
[
synchronized
](
https://javabetter.cn/thread/synchronized-1.html
)
是一招鲜的解决方案:
...
...
docs/thread/reentrantLock.md
浏览文件 @
02ce418b
---
title
:
深入理解Java并发重入锁ReentrantLock
shortTitle
:
重入锁ReentrantLock
description
:
深入理解Java并发重入锁ReentrantLock
description
:
重入锁(ReentrantLock)是 Java 并发编程中的一个重要概念。它是一种同步机制,提供了与 synchronized 关键字相同的基本行为,但更灵活,功能也更丰富。
category
:
-
Java核心
tag
:
...
...
@@ -12,17 +12,20 @@ head:
content
:
Java,并发编程,多线程,Thread,锁,ReentrantLock
---
#
深入理解Java并发重入锁
ReentrantLock
#
14.15 重入锁
ReentrantLock
ReentrantLock
重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,
**支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁
再次获取不会被阻塞**
。
ReentrantLock
重入锁,是实现
[
Lock 接口
](
https://javabetter.cn/thread/suo.html
)
的一个类,也是在实际编程中使用频率很高的一个锁,
**支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁后
再次获取不会被阻塞**
。
要想支持重入性,就要解决两个问题:
1.
在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;
2.
由于锁会被获取 n 次,那么只有锁在被释放同样的 n 次之后,该锁才算是完全释放成功。
2.
由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功
。
我们知道,同步组件主要是通过重写
[
AQS
](
https://javabetter.cn/thread/aqs.html
)
的几个 protected 方法来表达自己的同步语义
。
我们知道,同步组件主要是通过重写AQS的几个protected方法来表达自己的同步语义。针对第一个问题,我们来看看ReentrantLock是怎样实现的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:
### ReentrantLock 的源码分析
针对第一个问题,我们来看看 ReentrantLock 是怎样实现的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为内部类 Sync 的 nonfairTryAcquire 方法:
```
java
final
boolean
nonfairTryAcquire
(
int
acquires
)
{
...
...
@@ -48,7 +51,7 @@ final boolean nonfairTryAcquire(int acquires) {
}
```
这段代码的逻辑
也很简单,具体请看注释。为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的了?(依然还是以非公平锁为例)核心方法为
tryRelease:
这段代码的逻辑
很简单,具体请看注释。为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加 1 返回 true,表示可以再次获取成功。每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的呢?(依然还是以非公平锁为例)核心方法为
tryRelease:
```
java
protected
final
boolean
tryRelease
(
int
releases
)
{
...
...
@@ -67,30 +70,26 @@ protected final boolean tryRelease(int releases) {
return
free
;
}
```
代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。到现在我们可以理清ReentrantLock重入性的实现了,也就是理解了同步语义的第一条。
代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为 0 时锁才算成功释放,否则锁仍未释放。如果锁被获取了 n 次,释放了 n-1 次,该锁未完全释放返回 false,只有被释放 n 次才算成功释放,返回 true。到现在我们可以理清 ReentrantLock 重入性的实现了,也就是理解了同步语义的第一条。
ReentrantLock
支持两种锁:
**公平锁**
和
**非公平锁**
。
**何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO**
。ReentrantLock
的构造方法无参时是构造非公平锁,源码为:
ReentrantLock
支持两种锁:
**公平锁**
和
**非公平锁**
。
**何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足 FIFO**
。ReentrantLock
的构造方法无参时是构造非公平锁,源码为:
```
java
public
ReentrantLock
()
{
sync
=
new
NonfairSync
();
}
```
另外还提供了
另外一种方式,可传入一个boolean值,true时为公平锁,false
时为非公平锁,源码为:
另外还提供了
一种方式,可传入一个 boolean 值,true 时为公平锁,false
时为非公平锁,源码为:
```
java
public
ReentrantLock
(
boolean
fair
)
{
sync
=
fair
?
new
FairSync
()
:
new
NonfairSync
();
}
```
在
上面非公平锁获取时(nonfairTryAcquire方法)只是简单的获取了一下当前状态
做了一些逻辑处理,并没有考虑到当前同步队列中线程等待的情况。我们来看看公平锁的处理逻辑是怎样的,核心方法为:
在
非公平锁获取时(nonfairTryAcquire 方法),只是简单的获取了一下当前状态然后
做了一些逻辑处理,并没有考虑到当前同步队列中线程等待的情况。我们来看看公平锁的处理逻辑是怎样的,核心方法为:
```
java
protected
final
boolean
tryAcquire
(
int
acquires
)
{
...
...
@@ -113,23 +112,139 @@ protected final boolean tryAcquire(int acquires) {
return
false
;
}
```
这段代码的逻辑与
nonfairTryAcquire基本上一直,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断,方法名就可知道该方法用来判断当前节点在同步队列中是否有前驱节点的判断,如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,再才有做后面的
逻辑判断的必要性。
这段代码的逻辑与
nonfairTryAcquire 基本上一致,唯一的不同在于增加了 hasQueuedPredecessors 的逻辑判断,从方法名就可以知道该方法用来判断当前节点在同步队列中是否有前驱节点的,如果有前驱节点,说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点,才有做后面
逻辑判断的必要性。
**公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁**
。
---
### ReentrantLock 的使用
ReentrantLock 的使用方式与 synchronized 关键字类似,都是通过加锁和释放锁来实现同步的。我们来看看 ReentrantLock 的使用方式,以非公平锁为例:
```
java
public
class
ReentrantLockTest
{
private
static
final
ReentrantLock
lock
=
new
ReentrantLock
();
private
static
int
count
=
0
;
public
static
void
main
(
String
[]
args
)
throws
InterruptedException
{
Thread
thread1
=
new
Thread
(()
->
{
for
(
int
i
=
0
;
i
<
10000
;
i
++)
{
lock
.
lock
();
try
{
count
++;
}
finally
{
lock
.
unlock
();
}
}
});
Thread
thread2
=
new
Thread
(()
->
{
for
(
int
i
=
0
;
i
<
10000
;
i
++)
{
lock
.
lock
();
try
{
count
++;
}
finally
{
lock
.
unlock
();
}
}
});
thread1
.
start
();
thread2
.
start
();
thread1
.
join
();
thread2
.
join
();
System
.
out
.
println
(
count
);
}
}
```
代码很简单,两个线程分别对 count 变量进行 10000 次累加操作,最后输出 count 的值。我们来看看运行结果:
```
20000
```
可以看到,两个线程对 count 变量进行了 20000 次累加操作,说明 ReentrantLock 是支持重入性的。我们再来看看公平锁的使用方式,只需要将 ReentrantLock 的构造方法改为公平锁即可:
```
java
private
static
final
ReentrantLock
lock
=
new
ReentrantLock
(
true
);
```
运行结果为:
```
20000
```
可以看到,公平锁的运行结果与非公平锁的运行结果一致,这是因为公平锁的实现方式与非公平锁的实现方式基本一致,只是在获取锁时增加了判断当前节点是否有前驱节点的逻辑判断。
-
公平锁: 按照线程请求锁的顺序获取锁,即先到先得。
-
非公平锁: 线程获取锁的顺序可能与请求锁的顺序不同,可能导致某些线程获取锁的速度较快。
需要注意的是,使用 ReentrantLock 时,必须在 finally 块中手动释放锁。
>编辑:沉默王二,内容大部分来源以下三个开源仓库:
>- [深入浅出 Java 多线程](http://concurrent.redspider.group/)
>- [并发编程知识总结](https://github.com/CL0610/Java-concurrency)
>- [Java八股文](https://github.com/CoderLeixiaoshuai/java-eight-part)
### Condition 接口
----
[
Condition 接口
](
https://javabetter.cn/thread/condition.html
)
是与 Lock 绑定的,可以理解为一个 Lock 对象可以绑定多个 Condition 对象,Condition 接口提供了类似于 Object 的 wait、notify、notifyAll 等方法,与 Lock 一起使用可以实现等待/通知模式,比如实现一个阻塞队列:
GitHub 上标星 8700+ 的开源知识库《
[
二哥的 Java 进阶之路
](
https://github.com/itwanger/toBeBetterJavaer
)
》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:
[
太赞了,GitHub 上标星 8700+ 的 Java 教程
](
https://javabetter.cn/overview/
)
```
java
public
class
BlockingQueue
<
T
>
{
private
final
Lock
lock
=
new
ReentrantLock
();
private
final
Condition
notFull
=
lock
.
newCondition
();
private
final
Condition
notEmpty
=
lock
.
newCondition
();
private
final
Object
[]
items
=
new
Object
[
100
];
private
int
putptr
,
takeptr
,
count
;
public
void
put
(
T
t
)
throws
InterruptedException
{
lock
.
lock
();
try
{
while
(
count
==
items
.
length
)
{
notFull
.
await
();
}
items
[
putptr
]
=
t
;
if
(++
putptr
==
items
.
length
)
{
putptr
=
0
;
}
++
count
;
notEmpty
.
signal
();
}
finally
{
lock
.
unlock
();
}
}
public
T
take
()
throws
InterruptedException
{
lock
.
lock
();
try
{
while
(
count
==
0
)
{
notEmpty
.
await
();
}
Object
x
=
items
[
takeptr
];
if
(++
takeptr
==
items
.
length
)
{
takeptr
=
0
;
}
--
count
;
notFull
.
signal
();
return
(
T
)
x
;
}
finally
{
lock
.
unlock
();
}
}
}
```
代码很简单,就是一个阻塞队列的实现,put 方法用来向队列中添加元素,take 方法用来从队列中获取元素。我们来看看 put 方法的实现,首先获取锁,然后判断队列是否已满,如果已满则调用
`notFull.await()`
方法阻塞当前线程,直到队列不满,然后将元素添加到队列中,最后调用
`notEmpty.signal()`
方法唤醒一个等待的线程。take 方法的实现与 put 方法类似,不再赘述。
### 与 synchronized 关键字的比较
ReentrantLock 与 synchronized 关键字都是用来实现同步的,那么它们之间有什么区别呢?我们来看看它们的对比:
-
**ReentrantLock 是一个类,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现**
;
-
**ReentrantLock 可以实现选择性通知(锁可以绑定多个 Condition),而 synchronized 只能唤醒一个线程或者唤醒全部线程**
;
-
**ReentrantLock 是可重入锁,而 synchronized 不是**
;
-
ReentrantLock 必须手动释放锁。通常需要在 finally 块中调用 unlock 方法以确保锁被正确释放。synchronized: 自动释放锁。当同步块执行完毕时,JVM 会自动释放锁,不需要手动操作。
-
ReentrantLock: 通常提供更好的性能,特别是在高竞争环境下。ynchronized: 在某些情况下,性能可能稍差一些,但在现代 JVM 实现中,性能差距通常不大。
---
GitHub 上标星 8700+ 的开源知识库《
[
二哥的 Java 进阶之路
](
https://github.com/itwanger/toBeBetterJavaer
)
》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:
[
太赞了,GitHub 上标星 8700+ 的 Java 教程
](
https://javabetter.cn/overview/
)
微信搜
**沉默王二**
或扫描下方二维码关注二哥的原创公众号沉默王二,回复
**222**
即可免费领取。
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录