本文可作为传智播客《张孝祥-Java多线程与并发库高级应用》的学习笔记。

这一节我们做一个缓存系统。

在读本节前

请先阅读

ReentrantReadWriteLock读写锁的使用1

第一版

public class CacheDemo {

    private Map<String, Object> cache = new HashMap<String, Object>();
    public static void main(String[] args) {
        CacheDemo cd = new CacheDemo();
        System.out.println("ss   "+cd.getData2("ss"));
        System.out.println("ss   "+cd.getData2("ss"));
        System.out.println("mm   "+cd.getData2("mm"));
        System.out.println("mm   "+cd.getData2("mm"));
    }

    public Object getData2(String key){
        Object o=cache.get(key);
        if (o==null) {        //标识1
            System.out.println("第一次查  没有"+key);
            o=Math.random();     //实际上从数据库中获得
            cache.put(key, o);
        }
        return o;
    }
}

运行结果:

第一次查  没有ss

ss   0.4045014284225158

ss   0.4045014284225158

第一次查  没有mm

mm   0.9994663041529088

mm   0.9994663041529088



似乎没有问题。

你觉得呢?

如果有三个线程同时第一次到了getData2()的标识1处(所查找的key也都一样),一检查o为null,然后就去查数据库。在这种情况下,就等于三个线程查同一个key,然后都去了数据库。

这显然是不合理的。

第二版 synchronized

最简单的办法

    public synchronized Object getData2(String key){

        //.....

}

第三版 锁

上一节我们提到了ReentrantLock可以替换synchronized,前者是一种更为面向对象的设计。那我们试试。

    private Lock l=new ReentrantLock(); //l作为CacheDemo的成员变量

    public Object getData3(String key) {
        l.lock();
        Object o=null;
        try {
            o = cache.get(key);
            if (o == null) { // 标识1
                System.out.println("第一次查  没有" + key);
                o = Math.random(); // 实际上从数据库中获得
                cache.put(key, o);
            }
        } finally {
            l.unlock();
        }
        return o;
    }

第三版可以吗?

可以个p。

为什么,自己想。

我们得使用ReentrantReadWriteLock,在同一个方法中既有读锁也有写锁。



首先我又一个问题:

对同一个线程,可以在加了读锁后,没有解开读锁前,再加写锁吗?

换句话说,下面的代码会停吗?

public class CacheDemo {

    private Map<String, Object> cache = new HashMap<String, Object>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    public static void main(String[] args) {
        CacheDemo cd = new CacheDemo();
        cd.getData2();
    }

    public void getData2(){
        rwl.readLock().lock();
        System.out.println(1);
        rwl.writeLock().lock();
        System.out.println(2);
        rwl.readLock().unlock();
        System.out.println(3);
        rwl.writeLock().unlock();
        System.out.println(4);
    }
}

亲自试一下,控制台输出1后,程序就不动了。



说明加写锁前,读锁得先解开。

第四版

即有读锁,又有写锁

   

public static void main(String[] args) {
        CacheDemo cd = new CacheDemo();
        System.out.println("ss   "+cd.getData4("ss"));
        System.out.println("ss   "+cd.getData4("ss"));

        System.out.println("mm   "+cd.getData4("mm"));
        System.out.println("mm   "+cd.getData4("mm"));
    }

    public Object getData4(String key){
        rwl.readLock().lock();
        Object o=null;
        try {
            o=cache.get(key);
            if (o==null) {
                System.out.println("第一次查  没有"+key);
                rwl.readLock().unlock();  //标识4
                rwl.writeLock().lock();
                try {
                    if(value==null){  //标识0
                       value = "aaaa";//实际调用 queryDB();
                       cache.put(key,value);
                    }
                } finally {
                    rwl.writeLock().unlock();
                }
                rwl.readLock().lock(); //标识1
            }
        }finally {
            rwl.readLock().unlock();  //标注2
        }
        return o;
    }                 

结果

第一次查  没有ss

ss   0.5989899889645358

ss   0.5989899889645358

第一次查  没有mm

mm   0.8534424949014686

mm   0.8534424949014686

这个还有三个问题

1为什么在标识2处还得解锁。

这个答案在上一节已经提过。



2为什么在标识1出还得加锁?

如果标识1处不加锁,且程序一直正常执行,那么到标识2处,它解谁的锁?





3为什么标识0出还要检查一次?

如果同时又三个线程运行到标识4(查找相同的key),其中一个获得写锁,写入数据后,释放了写锁。此时另外两个线程还需要去数据库跑一趟么?



另外把标识1处的加读锁,放到finally的前面称之为降级锁。对降级锁,我目前也不太清楚。

官方文档中给了一个例子就是关于降级锁,如下

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

感谢glt

ReentrantReadWriteLock读写锁的使用2的更多相关文章

  1. ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  2. 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  3. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  4. ReentrantReadWriteLock读写锁简单原理案例证明

    ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...

  5. ReentrantReadWriteLock读写锁详解

    一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁.在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源:但是如果一个线 ...

  6. java多线程:并发包中ReentrantReadWriteLock读写锁的锁降级模板

    写锁降级为读锁,但读锁不可升级或降级为写锁. 锁降级是为了让当前线程感知到数据的变化. //读写锁 private ReentrantReadWriteLock lock=new ReentrantR ...

  7. java中ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  8. java多线程:ReentrantReadWriteLock读写锁使用

    Lock比传统的线程模型synchronized更多的面向对象的方式.锁和生活似,应该是一个对象.两个线程运行的代码片段要实现同步相互排斥的效果.它们必须用同一个Lock对象. 读写锁:分为读锁和写锁 ...

  9. ReentrantReadWriteLock读写锁的使用1

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 一个简单的例子 两个线程,一个不断打印a,一个不断打印b public class LockTest { publ ...

随机推荐

  1. Linux 高性能服务器编程——TCP协议详解

    问题聚焦:     本节从如下四个方面讨论TCP协议:     TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流     TCP状态转移过程:TCP连接的任意一 ...

  2. 给定一个数列a1,a2,a3,...,an和m个三元组表示的查询,对于每个查询(i,j,k),输出ai,ai+1,...,aj的升序排列中第k个数。

    给定一个数列a1,a2,a3,...,an和m个三元组表示的查询,对于每个查询(i,j,k),输出ai,ai+1,...,aj的升序排列中第k个数. #include <iostream> ...

  3. Java中导出到Excel实现_aspose.cells

    参考http://183615215-qq-com.iteye.com/blog/1858208 包下载:http://pan.baidu.com/s/1o6ju0ZK,将lib的jar包导入到工程中 ...

  4. linux下查看Memcached运行状态

    查看Memcached运行状态的命令是:echo stats | nc 127.0.0.1 11211 查看memcached状态的基本命令,通过这个命令可以看到如下信息: STAT pid 2245 ...

  5. UNIX网络编程——客户/服务器程序设计示范(二)

        TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...

  6. Android程序崩溃异常处理框架

    目前我已经把框架放在了github了,地址如下:https://github.com/msdx/android-crash 使用方法见github上面的README.md. 本项目相关jar包已发布在 ...

  7. iOS中 动态启动图GIF的简单设置 韩俊强的博客

    // 设定位置和大小 CGRect frame = CGRectMake(50,340,[UIScreen mainScreen].bounds.size.width / 2,[UIScreen ma ...

  8. 程序员大牛 Jeff Atwood 的两本中文书

    程序员大牛,StackOverflow.com创始人之一--Jeff Atwood 英文博客:http://blog.codinghorror.com <高效能程序员的修炼>,人民邮电出版 ...

  9. redhat安装vsftpd

    一个小问题 rpm -qa|ftp 但是出现3个ftp 只安装了一个 关于网卡ip 首先,我们看到 网卡上面有个x 说明网络是有问题的 我们双击,看到 我们先把connected和connect at ...

  10. 【自制插件】将MMD4Mecanim转换的MMD模型导入maya

    这个已经废弃了_(:зゝ∠)_,另外做了升级版: http://www.cnblogs.com/marisa/p/5174150.html ============================== ...