多线程的锁操作——ReentrantReadWriteLock类

1 ReadWriteLock接口

本文接上一篇的ReentrantLock类, 讲一讲 ReadWriteLock 接口及其实现类ReentrantReadWriteLock。

ReadWriteLock 也是一个接口,提供了 readLockwriteLock 两种不同用途锁的操作,该接口定义在java.util.concurrent.locks.ReadWriteLock,定义如下:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

通过其定义,大概总结如下:

  1. ReadWriteLock 并不是 Lock 的子接口,而是一种新的锁接口,只是用到了Lock接口作为成员方法返回值,实现其特定功能
  2. ReadWriteLock 提供了两个锁——读取锁、写入锁;其中每次读取共享数据时需要使用读取锁,当需要修改共享数据时就需要使用写入锁

2 ReentrantReadWriteLock实现类

ReentrantReadWriteLockReadWriteLock 接口的实现类。从ReentrantReadWriteLock 字面上来看包括了两重含义:一层含义是读写锁;另外一层含义是具有可重入性

  • 读写锁就是每次读取共享数据时使用读取锁,当需要修改共享数据时就使用写入锁。

  • 可重入性就是如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁;如果一个线程对资源加了读锁,其他线程可以继续加读锁。

综上,以下是对ReentrantReadWriteLock的读写锁机制的总结:

  • 读操作-读操作不互斥: 没有发生写操作,当多个线程同时执行读操作,那么这多个线程可以并发执行,不会发生阻塞
  • 写操作-读操作互斥: 当前正在发生写操作,那么此刻到来的读线程就会被阻塞
  • 读操作-写操作互斥: 当前正在发生读操作,那么此刻到来的写线程就会被阻塞
  • 写操作-写操作互斥: 当多个线程同时执行写操作时,某个线程先拿到锁就先执行,其他线程会被阻塞直到之前线程释放锁

3 ReentrantReadWriteLock的应用

关于ReentrantReadWriteLock的典型用法一般有两种。

3.1 读操作与写操作相分离的场景

//读写锁的典型用法
public class ReadAndWrite {
    //定义一个读写锁
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    //获取一个可以被多个读操作可共享的读取锁,同时互斥所有写操作
    private Lock  readLock = rwLock.readLock();
    //获取一个只能独占的写入锁,同时互斥所有的读操作与写操作
    private Lock writeLock = rwLock.writeLock();
    
    private int cost;
    
    //对所有读操作加读锁
    public int get(){
    //关于锁的用法与Lock的用法类似
    //需要配合try-finally使用
    readLock.lock();
    try{
        //TODO
    }finally{
        readLock.unlock();
    }
    return cost;
    }
    
    //对所有写操作加写锁
    public void set() {
    //关于锁的用法与Lock的用法类似
    //需要配合try-finally使用
    writeLock.lock();
    try {
        //TODO
        } finally {
        // 释放锁
        writeLock.unlock();
    }
    }
}

3.2 读操作与写操作相混合的场景

class CachedData {
    //缓存的数据内容
    Object data;
    //标识缓存数据是否准备好
    volatile boolean cacheValid;
    //读写锁
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        //为了先从缓存中获取数据,首先申请读锁
    rwl.readLock().lock();
        
    //如果缓冲中的数据还未准备好
    if (!cacheValid) {
        //由于缓冲中的数据还未准备好,我们首先需要从数据库中获取数据源
        //但是为了执行写操作,需要申请写锁,由于读锁与写锁互斥
        //需要首先是否读锁
        rwl.readLock().unlock(); //语句A
            
        //为了写入数据,需要首先申请写锁
        rwl.writeLock().lock(); 
        try {
            //需要再次判断,因为这里存在一种情况:比如之前有两个(甲与乙)读线程都执行到了语句A
        //其中甲线程获取到了写锁,开始执行;而乙线程被阻塞
        //等待甲执行完后,释放了写锁;乙线程获取到了写锁会继续执行
        //但是,此时甲线程已执行了语句B,已经将cacheValid置为了true,
        //下述的if判断可保证乙线程不再重复从数据库中获取数据源
        if (!cacheValid) {
            data = ...
            cacheValid = true;//语句B
        }
        //写操作完成时,必须进行锁降级,为什么是必须?请看下文
        //即:释放写锁之前先获取读锁
        rwl.readLock().lock();//语句C
         } finally {
            //释放写锁,但是仍然持有读锁
            rwl.writeLock().unlock(); 
        }
        }

        //缓冲的中的数据已准备好
        try {
            //用户的业务逻辑,使用数据
        use(data);
        } finally {
        //使用完数据后,最后释放读锁
            rwl.readLock().unlock();
        }
    }
}

如果当前线程不获取读锁而是直接释放写锁,假设此刻另外一个线程获取了写锁并修改了数据,那么当前线程无法感知后者线程的数据更新。如果当前线程获取了读锁,即遵循了锁降级的步骤,由于读-写互斥,后续线程就会被阻塞,直到当前线程使用数据并释放读锁后,后续线程线程才能获取写锁进行数据更新。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容

  • 在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制。Java提供了多种多线程锁机制的实现方...
    千淘萬漉阅读 6,916评论 1 33
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,653评论 2 17
  • 文/岑岚 文章目录:如果时光不记得——目录 前情回顾:如果时光不记得(十一) chapter12 酒足饭饱的时候,...
    岑岚阅读 1,050评论 0 13
  • 翻译1000字,晚上 8:00开始 主要做项目, 原来的数据集+现在的数据集上实验,找新的数据集,看文献 跑一跑胶...
    谁要陪我吃火锅阅读 42评论 0 1
  • 周六上午陪着女儿去上舞蹈课 课程结束后去旁边的月湖公园看银杏 满地的银杏叶很美 湖边斜柳的倒影很美 湖水随风荡漾 ...
    橄榄石阅读 178评论 0 0