提交 02ce418b 编写于 作者: 沉默王二's avatar 沉默王二 💬

读写锁

上级 d8c183dd
......@@ -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)
......
......@@ -291,10 +291,10 @@ export const sidebarConfig = sidebar({
"volatile",
"synchronized-1",
"synchronized",
"pianxiangsuo",
"cas",
"aqs",
"lock",
"pianxiangsuo",
"reentrantLock",
"ReentrantReadWriteLock",
"condition",
......
......@@ -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)
......
......@@ -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** 即可免费领取。
......
......@@ -12,7 +12,7 @@ head:
content: Java,并发编程,多线程,Thread,AQS
---
# 14.12 抽象队列同步器 AQS
# 14.13 抽象队列同步器 AQS
**AQS**`AbstractQueuedSynchronizer`的简称,即`抽象队列同步器`,从字面意思上理解:
......
......@@ -12,7 +12,7 @@ head:
content: Java,并发编程,多线程,Thread,cas
---
# 14.11 乐观锁 CAS
# 14.12 乐观锁 CAS
在并发编程中,我们都知道`i++`操作是非线程安全的,这是因为 `i++`操作不是原子操作,我们之前在讲[多线程带来了什么问题](https://javabetter.cn/thread/thread-bring-some-problem.html)中有讲到,大家应该还记得吧?
......
......@@ -12,7 +12,7 @@ head:
content: Java,并发编程,多线程,Thread,锁
---
# 14.13 JUC 包下的那些锁
# 14.14 JUC 包下的那些锁
前面我们介绍了 Java 原生的锁——基于对象的锁,它一般是配合 [synchronized 关键字](https://javabetter.cn/thread/synchronized-1.html)来使用的。实际上,Java 在`java.util.concurrent.locks`包下,还为我们提供了几个关于锁的类和接口。它们有更强大的功能或更高的性能。
......
......@@ -12,7 +12,7 @@ head:
content: Java,并发编程,多线程,Thread,偏向锁
---
# 14.14 JDK15 移除了偏向锁
# 14.11 JDK15 移除了偏向锁
在 JDK 1.5 之前,面对 Java 并发问题, [synchronized](https://javabetter.cn/thread/synchronized-1.html) 是一招鲜的解决方案:
......
---
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.
先完成此消息的编辑!
想要评论请 注册