并发读写的时候,很容易造成数据不一致的状态

上案例,代码如下:

public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int finali= i;
new Thread(() -> {
try {
myCache.put(finali+"", finali+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
} for (int i = 0; i < 5; i++) {
final int finali= i;
new Thread(() -> {
try {
myCache.get(finali+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
} class MyCache {
private volatile Map<String, Object> map = new HashMap<>(); public void put(String key, Object value) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "\t-----写入数据key");
Thread.sleep(3000);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t-----写入数据成功");
} public void get(String key) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "\t读取数据key");
Thread.sleep(3000);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t读取数据成功" + result);
}
}

运行结果如下:

我们可以看到的是在1进行写入数据的时候,此时还没有写入成功,就已经对1进行了读取操作,就像我们数据库的原子性一样,这里在还没有对数据进行写入完成就进行了读取的操作,所以读取的为空。
接下来我们看看加入了读写锁的效果,这里只需要对MyCache进行修改:

加入ReadWriteLock

ReadWriteLock的作用:保证并发读

  • 写锁是独占锁,所谓独占即为独自占有,别的线程既不能获取到该锁的写锁,也不能获取到对应的读锁。
  • 读锁是共享锁,所谓共享即是所有线程都可以共同持有该读锁
  • 归纳与一句换:读读共存 读写不共存 写写不共存

代码如下 :

class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void put(String key, Object value) throws InterruptedException {
readWriteLock.writeLock().lock(); //上写锁
try {
System.out.println(Thread.currentThread().getName() + "\t-----写入数据key");
Thread.sleep(3000);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t-----写入数据成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) throws InterruptedException {
readWriteLock.readLock().lock(); //上读锁
try {
System.out.println(Thread.currentThread().getName() + "\t读取数据key");
Thread.sleep(3000);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t读取数据成功" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}

运行结果如下:

可以看到我们对写保持了一致性,读保证了可并发读,防止了在写的时候进行了读的操作导致的不一致性,所以这就是读写锁的作用

ReadWriteLock锁降级

锁降级过程(当前线程):

为什么可以降级? 为什么在写锁释放之前可以拿到读锁?

首先写锁是独占的,读锁是共享的,然后读写锁是线程间互斥的,锁降级的前提是所有线程都希望对数据变化敏感,但是因为写锁只有一个,所以会发生降级。

你既然拿到写锁了,其他线程就没法拿到读锁或者写锁了,你再拿读锁,其实不会和其他线程的写锁发送冲突的,因为你拿到写锁到写锁释放这段时间其他线程是无法拿到任何锁的。

注意以下情况不是锁降级

如果先释放写锁,再获取读锁,可能在获取之前,会有其他线程获取到写锁,阻塞读锁的获取,就无法感知数据变化了。所以需要先hold住写锁,保证数据无变化,获取读锁,然后再释放写锁。

其他线程在该线程释放写锁之前,写操作所做的数据更新对其他线程是不可见的。但是一旦写锁释放,数据更新操作就会对其他线程可见。

思考?

即使是先释放写锁,然后获取读锁可能也没有问题,只不过会可能会被其他线程的写锁阻塞一段时间;

但是并不意味着,随后的这个读操作看不到之前别的线程的写锁下的写操作,只要写锁被释放数据更新还是可以看到的,所以说,上述这句话“阻塞读锁的获取,那么当前线程无法感知线程T的数据更新” 感觉有些瑕疵

但是需要明确的一点:用锁降级的前提是读优先于写。

比如常见的查询系统,需要保证数据的随时可读,如果当新线程请求读锁的时候,当前持有写锁的线程需要马上进行降级,保证所有读锁的顺利获取,阻塞后续写锁。

Java高并发,ReadWriteLock(读写锁)的更多相关文章

  1. java多线程 -- ReadWriteLock 读写锁

    写一条线程,读多条线程能够提升效率. 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作.只要没有 writer,读取锁 ...

  2. java 可重入读写锁 ReentrantReadWriteLock 详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt206 读写锁 ReadWriteLock读写锁维护了一对相关的锁,一个用于只 ...

  3. JAVA高并发程序设计笔记

    第二章 Java并行程序基础 1.join()的本质是让调用线程wait()在当前线程的对象上 2.Thread.yiedl()会使当前线程让出CPU 3.volatile保证可见性,无法保证原子性( ...

  4. Java高并发--AQS

    Java高并发--AQS 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 AQS是AbstractQueuedSynchronizer的简称,直译过来是抽象队列同步器. ...

  5. ReadWriteLock读写锁(八)

    前言:在JUC ReentrantReadWriteLock是基于AQS实现的读写锁实现. ReadWriteLock中定义了读写锁需要实现的接口,具体定义如下: public interface R ...

  6. 《实战Java高并发程序设计》读书笔记

    文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...

  7. 《实战Java高并发程序设计》读书笔记三

    第三章 JDK并发包 1.同步控制 重入锁:重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,这种锁可以反复使用所以叫重入锁. 重入锁和synchro ...

  8. Java高并发与多线程(四)-----锁

    今天,我们开始Java高并发与多线程的第四篇,锁. 之前的三篇,基本上都是在讲一些概念性和基础性的东西,东西有点零碎,但是像文科科目一样,记住就好了. 但是本篇是高并发里面真正的基石,需要大量的理解和 ...

  9. 《实战java高并发程序设计》源码整理及读书笔记

    日常啰嗦 不要被标题吓到,虽然书籍是<实战java高并发程序设计>,但是这篇文章不会讲高并发.线程安全.锁啊这些比较恼人的知识点,甚至都不会谈相关的技术,只是写一写本人的一点读书感受,顺便 ...

  10. ReadWriteLock: 读写锁

    ReadWriteLock: 读写锁 ReadWriteLock: JDK1.5提供的读写分离锁,采用读写锁分离可以有效帮助减少锁竞争. 特点: 1).使用读写锁.当线程只进行读操作时,可以允许多个线 ...

随机推荐

  1. mpi4py和cupy的联合应用(anaconda环境):GPU-aware MPI + Python GPU arrays

    Demo代码: from mpi4py import MPI import cupy as cp comm = MPI.COMM_WORLD size = comm.Get_size() rank = ...

  2. 键盘中上、下、左、右四个光标键所对应的ASCII码值为多少

    首先给出ASCII码值表: 上.下.左.右这四个光标键对应的ASCII码值不是一个值而是三个,准确的说光标键的ASCII码值是一个组合. 每个方向键所对应的三个键值为:0x1b + 0x5b + n ...

  3. 使用map方法递归替换组数对象内的某一个值

    const TreeDataSource = (arr) => { // 判断是否是数组 if (!arr || !arr.length > 0) { return } // 将值存入ma ...

  4. udp协议实现组播功能

    /*************************************************************************************************** ...

  5. Java jdk版本对照表

    这里将JDK版本和major.minor的版本的对照关系进行整理,作为今后查阅的依据. 序号 jdk版本 major.minor version 1 1.1 45 2 1.2 46 3 1.3 47 ...

  6. Jenkins配置分布式构建环境——添加固定Agent并使用JNLP启动Agent详解

    1.概述 在<Jenkins部署架构概述>这篇博文中对Jenkins部署架构进行了讲解.对于分布式架构,Jenkins包括固态Agent和动态Agent两种方案. 固定Agent(常用于虚 ...

  7. 第 356 场周赛 - 力扣(LeetCode)

    第 356 场周赛 - 力扣(LeetCode) 2798. 满足目标工作时长的员工数目 - 力扣(LeetCode) 一次遍历 class Solution { public: int number ...

  8. WPF使用Grid布局

    WPF布局 WPF布局基础 布局原则 一个窗口中只能包含一个元素 不应显示设置元素尺寸 不应使用坐标设置元素的位置 可以嵌套布局容器 布局容器 StackPanel: 水平或垂直排列元素.Orient ...

  9. Win32 自绘控件按钮类

    今天学了控件的自绘,初步偿试了下,蹂躏的不行不行的,查了好多的资料,头都弄大了, 有好多还是没弄明白,只是初步实现一个按钮的基本功能,好难呀, 先看下效果: 按下状态 弹起状态 按钮2按下状态 按钮2 ...

  10. Python开发工具:VSCode+插件

    本篇是 Python 系列教程第 3 篇,更多内容敬请访问我的 Python 合集 Visual Studio Code的安装非常简单,就不放这里增加文章篇幅了. 相比PyCharm,VSCode更加 ...