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. 2.mysql-库表行管理

    1.数据库管理 1.1 SQL语句 1.1.1 查看当前所有的数据库 show databases; 1.1.2 创建数据库 create database 数据库名; create databse ...

  2. Windows server 2008 tomcat间歇性掉线关闭

    1.代码没有问题,已经正常运行一年. 2.近期无更新代码. 3.tomcat 无运行报错. 今天突然间歇性掉线次数很多,客户不停反应情况,这边一时从代码开始找,我问了下在场运维其他服务器系统有无此现象 ...

  3. 【Linux】/proc/stat解析

    一. 概述 1.1 CPU时间 cpu指标 含义user 用户态时间nice 用户态时间(低优先级,nice>0)system 内核态时间idle 空闲时间iowait I/O等待时间irq 硬 ...

  4. OpenAI 推出超神 ChatGPT 注册教程来了

    前几天,OpenAI 推出超神 ChatGPT,非常火爆.但是呢,因为不可抗力原因,大部分人无法体验到.这里我分享一下注册的攻略. 准备 首先能能访问 Google(前置条件,不能明确说,懂得都懂) ...

  5. <二>vector向量容器

    底层数据结构:动态开辟的数组,每次以原始空间2倍扩容 vector vec; 增加 vec.push_back(100);容器末尾加元素 时间负责度O(1) 可能导致容器扩容 容器中的,对象的构造析构 ...

  6. json 提取器将提取的所有id拼接成字符串

    1.添加json提取器,提取所有的id 2.添加调试后置处理器查看buildid_ALL值以及类型 当需要将提取的id拼接成字符串时,此时使用json提取器并勾选Compute concatenati ...

  7. Jmeter 之 jp@gc - Stepping Thread Group

    jp@gc - Stepping Thread Group 自定义线程组,根据业务需要进行设计用户增加间隔时间等 1.  下载jmeter-plugins-manager-1.3.jar插件放入lib ...

  8. vue 点击按钮添加一行dom节点

    如图,最近项目需求,点击添加一行dom节点,包含下拉框和input输入框 ,下面展示一下代码 <ul class="sales-menuItem-ul"> <li ...

  9. 如何在路由绑定中使用 IParsable

    IParsable 是 .Net 7 中新增的接口,它可以将字符串转换为对应的实体.在 Controller 的 Route 绑定中可以使用 IParsable 来绑定复杂的实体. 实验背景 假设有一 ...

  10. 利用WordPress搭建属于自己的网站

    怎么用WordPress给自己搭建了一个网站?可能很多人都想拥有属于自己的网站,这篇文章就找你怎么利用WordPress搭建属于自己的网站.如果你也正好有搭建个人网站的想法,那么本文会给你一个参考,我 ...