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

上案例,代码如下:

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. centos7系统 通过编译安装gcc7.5.0

    背景: 现有的centos7 gcc的最高版本为4.8.5 项目需要升级到7.1.0以上 正常方式可以通过以下命令即可完成升级: $ sudo yum install centos-release-s ...

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

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

  3. games101 作业1及作业2分析及解决

    games101 作业1及作业2分析及解决 去年的时候把games101的课程以及作业完成,但是整个过程比较粗略,也借助了不少外界的力量(doge),于是最近准备抽几天集中再把作业(1-7)过一遍,常 ...

  4. MFC中CString转int,double

    CString str=L"123"; int n=_wtoi(str); //n=123 double d=_wtof(str); //d=123.0000 Vs2012中编译

  5. Linux samba的配置和使用

    samba是什么 samba是什么?能干什么?什么场合需要用到它? samba 是基于SMB协议(ServerMessage Block,信息服务块)的开源软件,samba也可以是SMB协议的商标.S ...

  6. ubuntu18.04 heirloom-mailx 通过外部SMTP服务器发送邮件

    配置软件源ubuntu18.04上无法直接安装heirloom-mailx,需要添加软件源 sudo vi /etc/apt/sources.list写入 deb http://cz.archive. ...

  7. ArgoWorkflow教程(三)---使用 Artifacts 实现步骤间文件共享

    上一篇我们分析了 Workflow.WorkflowTemplate.template 之间的关系.本篇主要分析如何在 argo-workflow 中使用 S3 存储 artifact 实现步骤之间的 ...

  8. Kubernetes-3.2:kubespray安装高可用k8sv1.20.2集群及常见报错解决

    kubespray安装高可用k8s集群 环境介绍 系统环境 主机名 / IP地址 角色 内核版本 CentOS 7.6.1810 master1 / 192.168.181.252 master &a ...

  9. 在虚拟机CentOS中安装jdk

    公众号本文地址:在虚拟机CentOS中安装jdk 本文主要是记录在CentOS中安装新的JDK的过程. 在虚拟机的centos中安装Jdk主要分为三步,第一步上传jdk文件到centos中,第二步解压 ...

  10. iptables NAT

    详解什么是NAT? IPtables中SNAT.DNAT和MASQUERADE的含义 Docker网络入门 – 默认设置