前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试系列之-读写锁(JAVA基础)

面试系列之-读写锁(JAVA基础)

作者头像
用户4283147
发布2023-09-11 15:55:09
2510
发布2023-09-11 15:55:09
举报
文章被收录于专栏:对线JAVA面试对线JAVA面试

读写锁的内部包含两把锁:一把是读(操作)锁,是一种共享锁;另一把是写(操作)锁,是一种独占锁。在没有写锁的时候,读锁可以被多个线程同时持有。写锁是具有排他性的:如果写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞;进一步来说,如果写锁被一个线程持有,其他的线程不能再持有读锁,抢占读锁也会阻塞。

读写锁的读写操作之间的互斥原则具体如下:

  • 读操作、读操作能共存,是相容的。
  • 读操作、写操作不能共存,是互斥的。
  • 写操作、写操作不能共存,是互斥的。

与单一的互斥锁相比,组合起来的读写锁允许对于共享数据进行更大程度的并发操作,虽然每次只能有一个写线程,但是同时可以有多个线程并发地读数据,读写锁适用于读多写少的并发情况。

代码语言:javascript
复制
public interface ReadWriteLock {
    /**
    * 返回读锁
    */
    Lock readLock();
    /**
    * 返回写锁
    */
    Lock writeLock();
}
读写锁ReentrantReadWriteLock

通过ReentrantReadWriteLock类能获取读锁和写锁,它的读锁是可以多线程共享的共享锁,而它的写锁是排他锁,在被占时不允许其他线程再抢占操作。然而其读锁和写锁之间是有关系的:同一时刻不允许读锁和写锁同时被抢占,二者之间是互斥的。

读写锁升级与降级

锁升级是指读锁升级为写锁,锁降级指的是写锁降级为读锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁。ReentrantReadWriteLock不支持读锁的升级,主要是避免死锁,例如两个线程A和B都占了读锁并且都需要升级成写锁,A升级要求B释放读锁,B升级要求A释放读锁,二者就会由于相互等待形成死锁。

代码语言:javascript
复制
public class ReadWriteLockTest2{
    //创建一个Map,代表共享数据
    final static Map<String, String> MAP = new HashMap<String, String>();
    //创建一个读写锁
    final static ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
    //获取读锁
    final static Lock READ_LOCK = LOCK.readLock();
    //获取写锁
    final static Lock WRITE_LOCK = LOCK.writeLock();
    //对共享数据的写操作
    public static Object put(String key, String value){
        WRITE_LOCK.lock();
        try{
            Print.tco(DateUtil.getNowTime()
                      + " 抢占了WRITE_LOCK,开始执行write操作");
            Thread.sleep(1000);
            String put = MAP.put(key, value);
            Print.tco( "尝试降级写锁为读锁");
            //写锁降级为读锁(成功)
            READ_LOCK.lock();
            Print.tco( "写锁降级为读锁成功");
            return put;
        } catch (Exception e){
            e.printStackTrace();
        } finally{
            READ_LOCK.unlock();
            WRITE_LOCK.unlock();
        }
        return null;
    }
    //对共享数据的读操作
    public static Object get(String key){
        READ_LOCK.lock();
        try{
            Print.tco(DateUtil.getNowTime()
                      + " 抢占了READ_LOCK,开始执行read操作");
            Thread.sleep(1000);
            String value = MAP.get(key);
            Print.tco( "尝试升级读锁为写锁");
            //读锁升级为写锁(失败)
            WRITE_LOCK.lock();
            Print.tco("读锁升级为写锁成功");
            return value;
        } catch (InterruptedException e){
            e.printStackTrace();
        } finally{
            WRITE_LOCK.unlock();
            READ_LOCK.unlock();
        }
        return null;
    }
    public static void main(String[] args){
        //创建Runnable可执行实例
        Runnable writeTarget = () -> put("key", "value");
        Runnable readTarget = () -> get("key");
        //创建1个写线程,并启动
        new Thread(writeTarget, "写线程").start();
        //创建1个读线程
        new Thread(readTarget, "读线程").start();
    }
}
StampedLock

StampedLock(印戳锁)是对ReentrantReadWriteLock读写锁的一种改进,主要的改进为:在没有写只有读的场景下,StampedLock支持不用加读锁而是直接进行读操作,最大程度提升读的效率,只有在发生过写操作之后,再加读锁才能进行读操作。StampedLock的三种模式如下:

  1. 悲观读锁:与ReadWriteLock读锁类似,多个线程可同时获取悲观读锁,悲观读锁是一个共享锁。
  2. 乐观读锁:相当于直接操作数据,不加任何锁,连读锁都不要。
  3. 写锁:与ReadWriteLock的写锁类似,写锁和悲观读锁是互斥的。虽然写锁与乐观读锁不会互斥,但是在数据被更新之后,之前通过乐观读锁获得的数据已经变成了脏数据。

StampedLock与ReentrantReadWriteLock语义类似,不同的是,StampedLock并没有实现ReadWriteLock接口,而是定义了自己的锁操作API。

代码语言:javascript
复制
public class StampedLockTest{
    //创建一个Map,代表共享数据
    final static Map<String, String> MAP = new HashMap<String, String>();
    //创建一个印戳锁
    final static StampedLock STAMPED_LOCK = new StampedLock();
    //对共享数据的写操作
    public static Object put(String key, String value){
        long stamp = STAMPED_LOCK.writeLock(); //尝试获取写锁的印戳
        try{
            Print.tco(getNowTime() + " 抢占了WRITE_LOCK,开始执行write操作");
            Thread.sleep(1000);
            String put = MAP.put(key, value);
            return put;
        } catch (Exception e){
            e.printStackTrace();
        } finally{
            Print.tco(getNowTime() + " 释放了WRITE_LOCK");
            STAMPED_LOCK.unlockWrite(stamp); //释放写锁
        }
        return null;
    }
    //对共享数据的悲观读操作
    public static Object pessimisticRead(String key){
        Print.tco(getNowTime() + "LOCK进入过写模式,只能悲观读");
        //进入了写锁模式,只能获取悲观读锁
        long stamp = STAMPED_LOCK.readLock(); //尝试获取读锁的印戳
        try{
            //成功获取到读锁,并重新获取最新的变量值
            Print.tco(getNowTime() + " 抢占了READ_LOCK");
            String value = MAP.get(key);
            return value;
        } finally{
            Print.tco(getNowTime() + " 释放了READ_LOCK");
            STAMPED_LOCK.unlockRead(stamp); //释放读锁
        }
    }
    //对共享数据的乐观读操作
    public static Object optimisticRead(String key){
        String value = null;
        //尝试进行乐观读
        long stamp = STAMPED_LOCK.tryOptimisticRead();
        if (0 != stamp){
            Print.tco(getNowTime() + "乐观读的印戳值,获取成功");
            sleepSeconds(1); //模拟耗费时间1秒
            value = MAP.get(key);
        } else // 0 == stamp 表示当前为写锁模式 {
            Print.tco(getNowTime() + "乐观读的印戳值,获取失败");
            //LOCK已经进入写模式,使用悲观读方法
            return pessimisticRead(key);
        }
        //乐观读操作已经间隔了一段时间,期间可能发生写入
        //所以,需要验证乐观读的印戳值是否有效,即判断LOCK是否进入过写模式
        if (!STAMPED_LOCK.validate(stamp)){
            //乐观读的印戳值无效,表明写锁被占用过
            Print.tco(getNowTime() + " 乐观读的印戳值,已经过期");
            //写锁已经被抢占,进入了写锁模式,只能通过悲观读锁再一次读取最新值
            return pessimisticRead(key);
        } else {
            //乐观读的印戳值有效,表明写锁没有被占用过
            //不用加悲观读锁而直接读,减少了读锁的开销
            Print.tco(getNowTime() + " 乐观读的印戳值,没有过期");
            return value;
        }
    }
    public static void main(String[] args) throws InterruptedException{
        //创建Runnable可执行实例
        Runnable writeTarget = () -> put("key", "value");
        Runnable readTarget = () -> optimisticRead("key");
        //创建1个写线程,并启动
        new Thread(writeTarget, "写线程").start();
        //创建1个读线程
        new Thread(readTarget, "读线程").start();
    }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 对线JAVA面试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 读写锁ReentrantReadWriteLock
  • 读写锁升级与降级
  • StampedLock
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档