线程基础知识14 ReentrantLock和ReentrantReadWriteLock
1 简介
ReentrantLock和ReentrantReadWriteLock都是可重入锁。可重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁
ReentrantLock和ReentrantReadWriteLock都支持获取锁时的公平和非公平性选择。默认是非公平的
ReentrantLock读读、读写、写写全部互斥。ReentrantReadWriteLock读读共享,读写互斥,写写互斥,且支持锁降级
ReentrantReadWriteLock由于读读共享,且支持锁降级,所及效率会高一些。由于它读写不共享,所以在读写高并发操作时,可能导致写的操作锁饥饿。
| 是否可重入 | 公平性选择 | 读读 | 读写 | 写写 | 锁降级 | |
| ReentrantLock | 是 | 是 | 互斥 | 互斥 | 互斥 | 不支持 |
| ReentrantReadWriteLock | 是 | 是 | 共享 | 互斥 | 互斥 | 支持 |
2 ReentrantLock示例
public class ReentrantLockTest1 {
static ReentrantLock lo = new ReentrantLock();
//读读互斥 读写互斥 写写互斥
public static void main(String[] args) {
for (int i = 0;i < 5;i++) {
new Thread(() -> operate(), "要进行读操作的线程" + i).start();
}
for (int i = 0;i < 5;i++) {
new Thread(() -> operate(), "要进行写操作的线程线程" + i).start();
}
}
private static void operate() {
lo.lock();
System.out.println(Thread.currentThread().getName() + "开始操作-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束操作-----------");
lo.unlock();
}
}
执行结果,只有执行完一个操作,才能够执行另一个操作,所有操作互斥
要进行读操作的线程0开始操作-----------
要进行读操作的线程0结束操作-----------
要进行读操作的线程1开始操作-----------
要进行读操作的线程1结束操作-----------
要进行读操作的线程2开始操作-----------
要进行读操作的线程2结束操作-----------
要进行读操作的线程3开始操作-----------
要进行读操作的线程3结束操作-----------
要进行读操作的线程4开始操作-----------
要进行读操作的线程4结束操作-----------
要进行写操作的线程线程0开始操作-----------
要进行写操作的线程线程0结束操作-----------
要进行写操作的线程线程1开始操作-----------
要进行写操作的线程线程1结束操作-----------
要进行写操作的线程线程2开始操作-----------
要进行写操作的线程线程2结束操作-----------
要进行写操作的线程线程3开始操作-----------
要进行写操作的线程线程3结束操作-----------
要进行写操作的线程线程4开始操作-----------
要进行写操作的线程线程4结束操作----------- Process finished with exit code 0
3 ReentrantReadWriteLock 示例
ReentrantReadWriteLock分为:
读锁-ReentrantReadWriteLock.ReadLock
写锁-ReentrantReadWriteLock.WriteLock
3.1 示例1
下面示例演示出了:读读不互斥,读写互斥,写写互斥
public class ReentrantLockTest2 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read线程" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write线程" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "开始写-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束写-----------");
writeLock.unlock();
}
}
执行结果,发现多个读操作可以同时进行,读写操作互斥,写写也互斥
read线程0开始读-----------
read线程4开始读-----------
read线程3开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程0结束读-----------
read线程3结束读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程2结束读-----------
write线程0开始写-----------
write线程0结束写-----------
write线程1开始写-----------
write线程1结束写-----------
write线程2开始写-----------
write线程2结束写-----------
write线程3开始写-----------
write线程3结束写-----------
write线程4开始写-----------
write线程4结束写----------- Process finished with exit code 0
3.2 示例2
这个示例是为了进一步演示读写互斥,和示例2相比,这里for循环先调用写,再调用的读,发现读写,还是互斥
public class ReentrantLockTest3 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 5;i++) {
new Thread(() -> write(), "write线程" + i).start();
}
for (int i = 0;i < 5;i++) {
new Thread(() -> read(), "read线程" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "开始写-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束写-----------");
writeLock.unlock();
}
}
执行结果
write线程0开始写-----------
write线程0结束写-----------
write线程1开始写-----------
write线程1结束写-----------
write线程2开始写-----------
write线程2结束写-----------
write线程3开始写-----------
write线程3结束写-----------
write线程4开始写-----------
write线程4结束写-----------
read线程0开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程4开始读-----------
read线程3开始读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程2结束读-----------
read线程0结束读-----------
read线程3结束读----------- Process finished with exit code 0
4 ReentrantReadWriteLock锁降级
简单来说,就是一个线程在持有写锁,且还未释放的时候,可以去获取读锁,这样子,该线程就可以同时持有读写锁。
多个线程操作一个变量a,使用ReentrantReadWriteLock线程1对a进行修改,值为100,它想要保证100这个值被其它所有的线程获取到,该怎么做?
那么我们要去写的时候,先去获取写锁(此时其它线程不能读写),写完了,再获取读锁(此时其它线程不能读写),此时同时持有读写锁,然后释放写锁,只持有读锁((此时其它线程不能写,但是可以读)),这个从写锁变为读锁的过程,就叫做锁降级。在持有读锁变为持有写锁的过程中,其它线程都不能写,保证我写的数据能够被其它线程看到。如果不能同时持有读写锁那么就只能这么操作,获取写锁-写-释放写锁-获取读锁-其它线程读-释放读锁,在释放写锁和获取读锁之间就会存在空隙,有可能被其它线程进行写操作,导致它写的结果不能被其他线程获取。

所以,锁降级解决的就是即写即读的问题
5 锁降级示例
5.1 示例1
在释放写锁后,释放读锁前,其它线程可读,且中间其它线程都不可写

public class ReentrantLockTest5 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write线程" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read线程" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "开始写-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束写结束写-----------");
readLock.lock(); //锁降级 同时持有写锁和读锁
System.out.println(Thread.currentThread().getName() + "锁降级-----------");
System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此时,它只拥有读锁
System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
readLock.unlock(); //
}
}
执行结果,可以看到write8在释放了写锁后释放读锁前,其它线程进来读了
write线程1开始写-----------
write线程1结束写结束写-----------
write线程1锁降级-----------
write线程1释放写锁-----------
write线程1释放读锁-----------
write线程0开始写-----------
write线程0结束写结束写-----------
write线程0锁降级-----------
write线程0释放写锁-----------
write线程0释放读锁-----------
write线程2开始写-----------
write线程2结束写结束写-----------
write线程2锁降级-----------
write线程2释放写锁-----------
write线程2释放读锁-----------
write线程3开始写-----------
write线程3结束写结束写-----------
write线程3锁降级-----------
write线程3释放写锁-----------
write线程3释放读锁-----------
write线程4开始写-----------
write线程4结束写结束写-----------
write线程4锁降级-----------
write线程4释放写锁-----------
write线程4释放读锁-----------
write线程5开始写-----------
write线程5结束写结束写-----------
write线程5锁降级-----------
write线程5释放写锁-----------
write线程5释放读锁-----------
write线程7开始写-----------
write线程7结束写结束写-----------
write线程7锁降级-----------
write线程7释放写锁-----------
write线程7释放读锁-----------
write线程6开始写-----------
write线程6结束写结束写-----------
write线程6锁降级-----------
write线程6释放写锁-----------
read线程1开始读-----------
read线程1结束读-----------
write线程6释放读锁-----------
write线程9开始写-----------
write线程9结束写结束写-----------
write线程9锁降级-----------
write线程9释放写锁-----------
read线程0开始读-----------
read线程0结束读-----------
write线程9释放读锁-----------
write线程8开始写-----------
write线程8结束写结束写-----------
write线程8锁降级-----------
write线程8释放写锁-----------
read线程7开始读-----------
read线程3开始读-----------
read线程5开始读-----------
read线程4开始读-----------
read线程8开始读-----------
read线程9开始读-----------
read线程2开始读-----------
read线程6开始读-----------
read线程9结束读-----------
read线程7结束读-----------
read线程3结束读-----------
read线程4结束读-----------
read线程6结束读-----------
read线程2结束读-----------
read线程8结束读-----------
read线程5结束读-----------
write线程8释放读锁----------- Process finished with exit code 0
5.2 示例2
在释放读锁前其它线程不可写

public class ReentrantLockTest4 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
public static void main(String[] args) {
for (int i = 0;i < 10;i++) {
new Thread(() -> write(), "write线程" + i).start();
}
for (int i = 0;i < 10;i++) {
new Thread(() -> read(), "read线程" + i).start();
}
}
private static void read() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
}
private static void write() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "开始写-----------");
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "结束写结束写-----------");
readLock.lock(); //锁降级 同时持有写锁和读锁
System.out.println(Thread.currentThread().getName() + "锁降级-----------");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
writeLock.unlock(); //释放写锁-此时值持有读锁
System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
readLock.unlock(); //释放读锁
}
}
执行结果
write线程1开始写-----------
write线程1结束写结束写-----------
write线程1锁降级-----------
write线程1释放写锁-----------
write线程1释放读锁-----------
write线程2开始写-----------
write线程2结束写结束写-----------
write线程2锁降级-----------
write线程2释放写锁-----------
write线程2释放读锁-----------
write线程0开始写-----------
write线程0结束写结束写-----------
write线程0锁降级-----------
write线程0释放写锁-----------
write线程0释放读锁-----------
write线程3开始写-----------
write线程3结束写结束写-----------
write线程3锁降级-----------
write线程3释放写锁-----------
write线程3释放读锁-----------
write线程4开始写-----------
write线程4结束写结束写-----------
write线程4锁降级-----------
write线程4释放写锁-----------
write线程4释放读锁-----------
write线程7开始写-----------
write线程7结束写结束写-----------
write线程7锁降级-----------
write线程7释放写锁-----------
write线程7释放读锁-----------
write线程8开始写-----------
write线程8结束写结束写-----------
write线程8锁降级-----------
write线程8释放写锁-----------
write线程8释放读锁-----------
write线程5开始写-----------
write线程5结束写结束写-----------
write线程5锁降级-----------
write线程5释放写锁-----------
write线程5释放读锁-----------
write线程6开始写-----------
write线程6结束写结束写-----------
write线程6锁降级-----------
write线程6释放写锁-----------
write线程6释放读锁-----------
write线程9开始写-----------
write线程9结束写结束写-----------
write线程9锁降级-----------
write线程9释放写锁-----------
write线程9释放读锁-----------
read线程0开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程3开始读-----------
read线程4开始读-----------
read线程5开始读-----------
read线程6开始读-----------
read线程7开始读-----------
read线程8开始读-----------
read线程9开始读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程0结束读-----------
read线程2结束读-----------
read线程5结束读-----------
read线程3结束读-----------
read线程6结束读-----------
read线程9结束读-----------
read线程7结束读-----------
read线程8结束读-----------
5.3 再来个更清晰的例子
持有读写锁,释放写锁后释放读锁前,其它线程可读
public class ReentrantLockTest8 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
//读读共享 读写互斥 写写互斥
public static void main(String[] args) {
CountDownLatch c = new CountDownLatch(10);
new Thread(() ->write(c), "write线程" ).start();
for (int i = 0;i < 10;i++) {
new Thread(() -> read(c), "read线程" + i).start();
}
}
private static void read(CountDownLatch c) {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
c.countDown();
}
private static void write(CountDownLatch c) {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "写-----------");
readLock.lock(); //锁降级 同时持有写锁和读锁
System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读
try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
readLock.unlock(); //释放读锁
}
}
执行结果,执行完成
write线程写-----------
write线程释放写锁-----------
read线程0开始读-----------
read线程5开始读-----------
read线程4开始读-----------
read线程4结束读-----------
read线程1开始读-----------
read线程1结束读-----------
read线程7开始读-----------
read线程7结束读-----------
read线程6开始读-----------
read线程5结束读-----------
read线程3开始读-----------
read线程9开始读-----------
read线程9结束读-----------
read线程2开始读-----------
read线程0结束读-----------
read线程2结束读-----------
read线程3结束读-----------
read线程6结束读-----------
read线程8开始读-----------
read线程8结束读-----------
write线程释放读锁----------- Process finished with exit code 0
持有读写锁,释放读锁和写锁前,其它线程不可读
把try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }放到释放写锁前
public class ReentrantLockTest7 {
static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
//读读共享 读写互斥 写写互斥
public static void main(String[] args) {
CountDownLatch c = new CountDownLatch(10);
new Thread(() ->write(c), "write线程" ).start();
for (int i = 0;i < 10;i++) {
new Thread(() -> read(c), "read线程" + i).start();
}
}
private static void read(CountDownLatch c) {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "开始读-----------");
System.out.println(Thread.currentThread().getName() + "结束读-----------");
readLock.unlock();
c.countDown();
}
private static void write(CountDownLatch c) {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "写-----------");
readLock.lock(); //锁降级 同时持有写锁和读锁
try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读
System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
readLock.unlock(); //释放读锁
}
}
执行结果,卡住了,没有执行完

线程基础知识14 ReentrantLock和ReentrantReadWriteLock的更多相关文章
- Java__线程---基础知识全面实战---坦克大战系列为例
今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...
- java线程基础知识----线程与锁
我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...
- java线程基础知识----线程基础知识
不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...
- Windows核心编程 第六章 线程基础知识 (上)
第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...
- Java并发之线程管理(线程基础知识)
因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...
- Java线程基础知识(状态、共享与协作)
1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...
- java线程基础知识----java daemon线程
java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...
- java并发编程(一)----线程基础知识
在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...
- Java 线程基础知识
前言 什么是线程?线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程 ID,当前指令指针 (PC),寄存器集合和堆栈组成.另外,线 ...
- Delphi线程基础知识
参考http://blog.chinaunix.net/uid-10535208-id-2949323.html 一.概述 Delphi提供了好几种对象以方便进行多线程编程.多线程应用程序有以下几方面 ...
随机推荐
- WPF之BackgroundWorker
BackgroundWorker类允许您在单独的线程上执行某个可能导致用户界面(UI)停止响应的耗时操作,下面来介绍一下这个线程类BackgroundWorker,大家可以结合这位大佬的这篇文章,说的 ...
- 匿名方法、Lambda表达和自定义泛型委托以及Func、Action系统泛型委托
1.匿名方法的概念:一个方法没有具体的名称,而只有关键字delegate.方法参数.方法体.这种方法是匿名方法. 匿名方法的好处:将具体方法和委托直接关联在一起,如果我们基于委托只需要一个方法的时候, ...
- 钉钉dingtalk=6.3.5版本RCE复现
看到网上公开了钉钉RCE的利用方式,第一时间来复现一下. 钉钉dingtalk=6.3.5版本RCE复现 免责声明: 影响版本: 漏洞POC: 漏洞复现: 存在漏洞版本下载地址: 免责声明: 本文章仅 ...
- ubuntu 22.04安装多个gcc
sudo apt install gcc-9 g++-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 - ...
- win 10 玩红警/黑边,不能全屏,闪退
win 10玩红警黑边问题 1.下载ddraw.dll,放在游戏目录 下载链接:ddraw.dll 如果提示 选择保留就行了 2.Win 键+S键,搜索注册表,打开这个 进去按这个路径 计算机\ ...
- <七>理解多态
理解多态 多种多样的形态(静态多态,动态多态) 静态多态(编译时期) 1:函数重载 bool comparet(int ,int); bool compare(double,double); comp ...
- Java开发学习(四十六)----MyBatisPlus新增语句之id生成策略控制及其简化配置
在前面有一篇博客:Java开发学习(四十一)----MyBatisPlus标准数据层(增删查改分页)开发,我们在新增的时候留了一个问题,就是新增成功后,主键ID是一个很长串的内容. 我们更想要的是按照 ...
- 【Java EE】Day09 JavaScript基础
一.JavaScript简介 二.JavaScript语法 三.JavaScript对象
- 【数据结构与算法】希尔排序 python和c++实现
算法思路 每一次:固定间隔把数据分组,每一组进行排序 每次比上次选取更小的间隔分组,再每组排序,直到间隔为1 代码 c++:(越看越不明白了,后看) int gap = length;//length ...
- React报错之Function components cannot have string refs
总览 当我们在一个函数组件中使用一个字符串作为ref时,会产生"Function components cannot have string refs"错误.为了解决该错误,使用u ...