【JUC并发编程09】读写锁
文章目录
- 9 读写锁
- 9.1 悲观锁和乐观锁
- 9.2 表锁|行锁|读锁|写锁
- 9.3 读写锁概述
- 9.4 读写锁的演变
- 9.5 锁降级的必要性
9 读写锁
9.1 悲观锁和乐观锁
悲观锁:顾名思义,它是干什么都很悲观,所以在操作的时候,每次都先上锁,使用时解锁
乐观锁:它很乐观,多线程,并不上锁,但是会发生线程安全问题,通过比较版本号来同步
9.2 表锁|行锁|读锁|写锁
表锁:整个表操作,不会发生死锁
行锁:每个表中的单独一行进行加锁,会发生死锁
读锁:共享锁(可以有多个人读),会发生死锁
写锁:独占锁(只能有一个人写),会发生死锁
比如说,
1线程修改时候,需要等待2线程读之后
2线程修改时候,需要等待1线程读之后
所以,1-2 线程发生死锁
9.3 读写锁概述
读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享
读写锁 ReentrantReadWriteLock
读锁为 ReentrantReadWriteLock.ReadLock,readLock()
方法
写锁为 ReentrantReadWriteLock.WriteLock,writeLock()
方法
创建读写锁对象 private ReadWriteLock rwLock = new ReentrantReadWriteLock();
写锁 加锁 rwLock.writeLock().lock();,解锁为rwLock.writeLock().unlock();
读锁 加锁 rwLock.readLock().lock();,解锁为rwLock.readLock().unlock();
在不加读写锁的情况下:
class MyCache{// 需要模仿从Map中取对象,所以先穿件一个map对象private volatile Map<String, Object> map = new HashMap<>();// 放数据public void put(String key, Object value) {try {System.out.println(Thread.currentThread().getName()+"正在写操作"+key);// 暂停一会TimeUnit.MICROSECONDS.sleep(300);// 放数据map.put(key, value);System.out.println(Thread.currentThread().getName()+"写完了"+key);} catch (InterruptedException e) {e.printStackTrace();}}// 取数据public void get(String key) {try {System.out.println(Thread.currentThread().getName()+"正在取操作"+key);// 暂停一会TimeUnit.MICROSECONDS.sleep(300);// 放数据map.get(key);System.out.println(Thread.currentThread().getName()+"取完了"+key);} catch (InterruptedException e) {e.printStackTrace();}}
}public class ReadWriteLockTest {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <= 6; i++) {final int num = i;new Thread(()->{myCache.put(String.valueOf(num),String.valueOf(num));},String.valueOf(i)).start();}for (int i = 1; i <= 6; i++) {final int num = i;new Thread(()->{myCache.get(String.valueOf(num));},String.valueOf(i)).start();}}
}
输出结果如下:
1正在写操作1
4正在写操作4
3正在写操作3
2正在写操作2
5正在写操作5
6正在写操作6
1正在取操作1
2正在取操作2
3正在取操作3
4正在取操作4
5正在取操作5
6正在取操作6
2取完了2
2写完了2
5写完了5
5取完了5
6取完了6
3写完了3
4写完了4
1写完了1
6写完了6
4取完了4
3取完了3
1取完了1
很显然,线程在写操作的时候,有线程在读操作,这可能会出现脏数据
加上读写锁:
class MyCache{// 需要模仿从Map中取对象,所以先穿件一个map对象private volatile Map<String, Object> map = new HashMap<>();// 创建读写锁private ReadWriteLock rwlock = new ReentrantReadWriteLock();// 放数据public void put(String key, Object value) {// 添加写锁rwlock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+"正在写操作"+key);// 暂停一会TimeUnit.MICROSECONDS.sleep(300);// 放数据map.put(key, value);System.out.println(Thread.currentThread().getName()+"写完了"+key);} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放写锁rwlock.writeLock().unlock();}}// 取数据public void get(String key) {// 添加读锁rwlock.readLock().lock();;try {System.out.println(Thread.currentThread().getName()+"正在取操作"+key);// 暂停一会TimeUnit.MICROSECONDS.sleep(300);// 放数据map.get(key);System.out.println(Thread.currentThread().getName()+"取完了"+key);} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放读锁rwlock.readLock().unlock();}}
}
得到的结果为:
2正在写操作2
2写完了2
1正在写操作1
1写完了1
3正在写操作3
3写完了3
4正在写操作4
4写完了4
5正在写操作5
5写完了5
6正在写操作6
6写完了6
1正在取操作1
2正在取操作2
5正在取操作5
3正在取操作3
6正在取操作6
4正在取操作4
1取完了1
3取完了3
2取完了2
4取完了4
6取完了6
5取完了5
9.4 读写锁的演变
无锁:多线程抢夺资源
synchronized和ReentrantLock,都是独占,每次只可以一个操作,不能共享
**ReentrantReadWriteLock,**读读可以共享,提升性能,但是不能多人写。缺点:造成死锁(一直读,不能写),读进程不能写,写进程可以读。
写锁降级为读锁(一般等级写锁高于读锁)
具体第四步演练的代码
获取写锁->获取读锁->释放写锁->释放读锁
//演示读写锁降级
public class Demo1 {public static void main(String[] args) {//可重入读写锁对象ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁//锁降级//1 获取写锁writeLock.lock();System.out.println("---write");//2 获取读锁readLock.lock();System.out.println("---read");//3 释放写锁writeLock.unlock();//4 释放读锁readLock.unlock();}
}
倘若先读再写,由于读写锁的特点,在读锁后不允许写,所以会被阻塞
9.5 锁降级的必要性
锁降级中读锁的获取是否必要呢?答案是必要的。
我们在使用读写锁时遵守下面的获取规则
1.如果有一个线程已经占用了读锁,则此时其他线程如果要申请读锁,可以申请成功。
2.如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,因为读写不能同时操作。
3.如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,都必须等待之前的线程释放写锁,同样也因为读写不能同时,并且两个线程不应该同时写
主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁, 假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
这时因为可能存在一个事务线程不希望自己的操作被别的线程中断,而这个事务操作可能分成多部分操作更新不同的数据(或表)甚至非常耗时。如果长时间用写锁独占,显然对于某些高响应的应用是不允许的,所以在完成部分写操作后,退而使用读锁降级,来允许响应其他进程的读操作。只有当全部事务完成后才真正释放锁。
所以总结下锁降级的意义应该就是:在一边读一边写的情况下提高性能。
如果是读之后再写,执行不了 因为读锁权限小于写锁 需要读完之后释放读锁,在进行写锁
【JUC并发编程09】读写锁相关推荐
- Java Review - 并发编程_读写锁ReentrantReadWriteLock的原理源码剖析
文章目录 ReentrantLock VS ReentrantReadWriteLock 类图结构 非公平的读写锁实现 写锁的获取与释放 void lock() void lockInterrupti ...
- Java并发编程-ReadWriteLock读写锁
1.ReadWriteLock介绍 为什么我们有了Lock,还要用ReadWriteLock呢.我们对共享资源加锁之后,所有的线程都将会等待.Lock读操作也锁,写操作也会锁,而对共享资源读的时候,其 ...
- Java 并发编程之读写锁 ReentrantReadWriteLock
ReentrantReadWriteLock 允许多个读操作同时进行,但是只允许一个写操作同时进行,在某些情况下,可以实现更高的并发性. 举个栗子: public class ReadAndWrite ...
- 【尚硅谷】大厂必备技术之JUC并发编程——笔记总结
[JUC并发编程01]JUC概述 关键字:进程和线程.进程和线程.wait和sleep.并发与并行.管程.用户线程和守护线程 [JUC并发编程02]Lock接口 关键字:synchronized.Lo ...
- JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快!
JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快! 一.ReentrantReadWriteLock(读写锁) 1.读写锁存在 ...
- ❤️《JUC并发编程从入门到高级》(建议收藏)❤️
JUC并发编程 1.什么是JUC JUC的意思就是java并发编程工具包,与JUC相关的有三个包:java.util.concurrent.java.util.concurrent.atomic.ja ...
- 多线程进阶=》JUC并发编程02
在JUC并发编程01中说到了,什么是JUC.线程和进程.Lock锁.生产者和消费者问题.8锁现象.集合类不安全.Callable(简单).常用辅助类.读写锁 https://blog.csdn.net ...
- 基于《狂神说Java》JUC并发编程--学习笔记
前言: 本笔记仅做学习与复习使用,不存在刻意抄袭. -------------------------------------------------------------------------- ...
- Java JUC并发编程详解
Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...
最新文章
- RouterOS连载3:架设PPPoE服务
- ganglia metric 默认监控项翻译
- Intellij IDEA SpringBoot项目热部署解决方案
- 逻辑判断 java_写 JS 逻辑判断,不要只知道用 if-else 和 switch
- vue用户行为收集_vue 实现移动端键盘搜索事件监听
- 语音社交产品,安全合规“防坑指南”!
- Centos下 自动化配置SSH免密码登陆
- 14 张Python数据科学速查表
- date类型_Quartz与Date---cron的相互转换
- 太可怕!儿童智能手表竟成偷窥器,315重锤个人隐私泄露
- 深度学习在其他领域的应用1:密码破解
- 低代码,是否能“取代”开发者?
- python需要下载哪些软件-学python下载什么软件开发
- Qt 学习之路 2(2):Qt 简介 笔记
- 前端基础学习之CSS样式
- 转 PHP函数---$_Get()和$_Post()的用法
- 转发:Android自动开关机实现
- 4571: [Scoi2016]美味
- NosqlBooster For MongoDB解决License问题
- GOME-2 SIF 数据链接