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. 陪你去看 Lodash.js 起步

    lodash 起步(数组) Lodash 是一个较为流行的 JavaScript 的实用工具库. 在开发过程中如果能熟练使用一些工具库提供的方法,有利于提高开发效率. 笔者从 API 上入手,不分析其 ...

  2. phpmyadmin 数据库导出数据到excel(图文版)

    查询到想要的数据后,点击上方或下方的"导出"按钮 格式选择"CSV for MS Excel" 如果快速导出的数据乱码,可以选择"导出方式" ...

  3. JavaWeb实战:基础CRUD+批量删除+分页+条件

    技术栈及相关参考资料: MyBatis基础 Servlet基础 ServletRequest和ServletResponse MVC模式和三层架构 AJAX基础+Axios基础 Vue前端框架 Ele ...

  4. JSP利用AJAX实现页面即时校验验证码

    在JSP页面实现验证码校验文章中当时是使用的Servlet类来进行的验证码校验,但是这种方式并不能即时校验,在正常情况下都是直接在用户输入之后就进行校验,这样对用户来说很方便的. AJAX 即&quo ...

  5. 项目上的业务《接收一个xml信息包进行解析,xml中包含base64解析为电子文件》

    我就直接贴代码了,不太会说,附上注释. ps:需要根据系统字段和xml里面的标签字段进行建表,之后把xml标签的值进行添加.创建表的方法就是拼的sql. // 在线接收接口 @Transactiona ...

  6. 【Devexpress】Gridcontorl动态创建列不显示的问题

    通过代码创建列但是不显示,这个原因是因为代码创建的列Visible属性默认是false 所以需要设置为true就会显示了 gridColumn.Visible = true;

  7. 【element】中el-row如何使内容垂直居中

    查阅官方文档,只需要在el-row中设置属性align为middle即可

  8. 第2-4-4章 规则引擎Drools规则属性-业务规则管理系统-组件化-中台

    目录 5. 规则属性 5.1 enabled属性 5.2 dialect属性 5.3 salience属性 5.4 no-loop属性 5.5 activation-group属性 5.6 agend ...

  9. 【每日一题】2021年12月14日-82. 删除排序链表中的重复元素 II

    存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字. 返回同样按升序排列的结果链表. 来源:力扣(LeetCo ...

  10. 嵌入式Linux Qt移植详细过程

    嵌入式Linux下的Qt移植详细过程 开发说明 前段时间需要用开发板写一个下位机程序,是基于Linux系统,就想着用Qt来写,于是上网找教程看如何移植到开发板上.由于我不熟悉嵌入式Linux,加上网上 ...