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. Go语言核心36讲04

    我们已经知道,环境变量GOPATH指向的是一个或多个工作区,每个工作区中都会有以代码包为基本组织形式的源码文件. 这里的源码文件又分为三种,即:命令源码文件.库源码文件和测试源码文件,它们都有着不同的 ...

  2. SQL-GROUP BY语句在MySQL中的一个错误使用被兼容的情况

    首先创建数据库hncu,建立stud表格. 添加数据: create table stud(sno varchar(30) not null primary key,sname varchar(30) ...

  3. ubuntu上升级cmake到3.16版本

    本来cmake的旧版本是2.8.12.2,现在更新到3.16.0版本. 需要文件:cmake 3.16.0压缩包,在附件. 1.  查看cmake版本:cmake --version 2.  解压cm ...

  4. Django基础笔记2(分页)

    Django Django自带的分页功能 from django.core.paginator import Paginator # 用于分页 curPage = request.GET.get('p ...

  5. 【Java SE进阶】Day02 Collection、Iterator、泛型

    一.Collection集合 1.概述 数组存元素,集合存对象(类型可以不一样) 2.框架分类 单列:Collection List ArrayList LinkedList Set HashSet ...

  6. 【每日一题】【DFS/回溯】2022年1月1日-113. 路径总和 II

    给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径. 叶子节点 是指没有子节点的节点. 来源:力扣(LeetCode)链接 ...

  7. 不用USB,通过adb无线调试安卓手机页面

    以前真机调试手机页面,都是使用数据线连接手机和电脑,近日身边没有USB数据线,折腾了下如何不依赖数据线只用无线调试手机页面,教程如下. 本教程适用于安卓11以及以上版本.否则应该使用USB数据线连接. ...

  8. 06.python闭包

    python闭包 什么样的函数是 闭包函数 ? 满足以下条件: 闭:外层函数嵌套了一个内层函数. 包:内层函数调用外层函数命名空间内的名字. 举例如下: def out_func(): # 外层函数 ...

  9. Karmada多云多集群生产实践专场圆满落幕

    摘要:CNCF Karmada社区Cloud Native Days China 2022南京站成功举办. 本文分享自华为云社区<Karmada多云多集群生产实践专场圆满落幕|Cloud Nat ...

  10. 使用Springboot+redis+Vue实现秒杀的一个Demo

    目录 1.Redis简介 2.实现代码 3.启动步骤 4.使用ab进行并发测试 5.线程安全 6.总结 7.参考资料 1.Redis简介 Redis是一个开源的key-value存储系统. Redis ...