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的更多相关文章

  1. Java__线程---基础知识全面实战---坦克大战系列为例

    今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...

  2. java线程基础知识----线程与锁

    我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...

  3. java线程基础知识----线程基础知识

    不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...

  4. Windows核心编程 第六章 线程基础知识 (上)

    第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...

  5. Java并发之线程管理(线程基础知识)

    因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...

  6. Java线程基础知识(状态、共享与协作)

    1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...

  7. java线程基础知识----java daemon线程

    java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...

  8. java并发编程(一)----线程基础知识

    在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...

  9. Java 线程基础知识

    前言 什么是线程?线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程 ID,当前指令指针 (PC),寄存器集合和堆栈组成.另外,线 ...

  10. Delphi线程基础知识

    参考http://blog.chinaunix.net/uid-10535208-id-2949323.html 一.概述 Delphi提供了好几种对象以方便进行多线程编程.多线程应用程序有以下几方面 ...

随机推荐

  1. [CS61A] Lecture 5&6&7. Environments & Design & Functions Examples & Homework 2: Higher Order Functions

    [CS61A] Lecture 5&6&7. Environments & Design & Functions Examples & Homework 2: ...

  2. .NET周报【11月第2期 2022-11-15】

    国内文章 统一的开发平台.NET 7正式发布 https://www.cnblogs.com/shanyou/archive/2022/11/09/16871945.html 在 2020 年规划的. ...

  3. element-ui 对话框dialog里使用echarts,报错'dom没有获取到'?

    给el-dialog添加@open="open()" 在刚进入页面的时候对话框是关闭的,echarts不进行获取dom,当点击对话框出来的时候,有个opened事件,在这个事件里边 ...

  4. 手动安装Mysql数据库,适合重装系统和绿色安装。

    依次输入命令: d:cd D:\MySQL Server 5.6\bin 根据自己的目录设置 mysqld --install mysql4001 --defaults-file="D:\m ...

  5. 轻松玩转sed

    sed处理文本方法 1.文本或管道输入 2.读入一行到模式控件 3.sed命令处理 4.输出到屏幕 所以 sed是一个流处理编辑器 sed一次处理一行内容 sed不改变文件内容(可以通过重定向改变文件 ...

  6. springMVC环境的搭建(一)

    概要: MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器(Controller). MV ...

  7. java面试题-线程

    简述线程.程序.进程的基本概念.以及他们之间关系是什么? 系统运行程序到停止就是一个进程创建到消亡的过程,而线程则是进程的更小单位 线程有哪些基本状态? 初始,运行中,等待,阻塞,超时,终止1 关注公 ...

  8. 5V升压12.6V芯片电路图,三节锂电池充电

    三节3.7V的锂电池串联,11.1V和最大12.6V锂电池充电电路的解决方案.在应用中,一般使用低压5V,如USB口直接输入的给三串锂电池充电,还有是15V或者18V,20V输入降压给锂电池充电的两种 ...

  9. troubleshoot:PVC动态扩容报错

    目录 一.问题描述 二.解决方法 一.问题描述 动态扩容PVC的时候报错(kubectl edit pvc pvcname):"error: persistentvolumeclaims & ...

  10. 铁威马NAS如何开启二次验证提高系统安全性

    想到登录TNAS时更安全?直接开启OTP二次验证,通过 TNAS mobile生成的一次性密码登录NAS存储,简单设置,提升TOS系统访问安全性给你TNAS双重保护. 1.首先,确认你的TOS系统在5 ...