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

上案例,代码如下:

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. Java实现微信登录(网页授权)

    1.背景 实际开发中,使用第三方登录是非常常见的业务... 这样可以大提高用户体验,没必要一来就要注册,或者登录之类的... 并且开发一个登录或者注册严格来说也是非常麻烦的(各种防止攻击.机器操作等) ...

  2. vue之es6语法

    1.背景 2.let与var与const的区别 <!DOCTYPE html> <html lang="en"> <head> <meta ...

  3. Sentry 开源版与商业 SaaS 版的区别

    您会在官方的文档中找到大量对 sentry 和 getsentry 的引用.两者都是 Django 应用程序,但 sentry 是开源的, getsentry 是闭源的.里面有什么? https:// ...

  4. Java反射初探123456789

    牛逼的框架都反射,不要问我为什么,因为我也不知道 可能是因为生成了class文件没法实例化,所以只能反射创建对象,但是在spring中,ioc就是反射实现的控制反转 看Spring4.x企业级开发实战 ...

  5. 08-canvas绘制表格

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  6. 2023 CCPC 哈尔滨游记

    board zsy 11.3 下了高代课跟教练聊了会,以为差点赶不上飞机了,结果还好.飞机上一直在看<笑傲江湖> 晚上本来想写作业的,还是摆了 拉 zsy 打雀魂,三人麻将到第二天了 11 ...

  7. 瑞芯微 | 摄像头ov13850移植笔记

    <1.瑞芯微rk356x板子快速上手> <2.Linux驱动|瑞芯微rtc-hym8563移植笔记> <3.Linux驱动 | Linux内核 RTC时间架构-基于瑞芯微 ...

  8. windows启动jar包并显示标题

    cmd启动java的jar并显示窗口标题 title xx服务 D: cd D:\xx服务 java -jar guns-vip-main.jar 文件名名为 run.bat 双击即可运行

  9. SSH 登陆 Windows 时踩过的坑

    有一次处于某些原因我在 Mac 上使用 SSH 远程登陆了 Windows,然后在 Windows 上使用 SSH 登陆 localhost,惊讶地发现登不进去!SSH 提示公钥验证失败.可是我的 W ...

  10. 安卓系统使用chrome插件(以yandex安装油猴为例)

    以tampermonkey为代表的Chrome插件广受好评,但由于Chrome在安卓系统并不支持令人遗憾.所以带来安卓手机使用Chrome插件的教程. 一,首先下载安卓开源浏览器(个人推荐yandex ...