面试官: 平时开发中你用过读写锁吗?

简介: 同程艺龙基础架构部推出的数据获取组件DAL.Connection,我们要做到在切换连接配置时清空数据库连接池, 这就涉及到切换连接的时候,触发变更通知。

这在高并发下会有问题:大多数时候下DBA并不会变更业务方的数据库连接,这是一个多读少写的场景, 我们无脑使用lock在多数时间会人为阻塞请求。


到这个时候,我们就要想到读写锁ReaderWriterLockSlim


宝藏好物:ReaderWriterLockSlim


Use ReaderWriterLockSlim to protect a resource that is read by multiple threads and written to by one thread at a time. ReaderWriterLockSlim allows multiple threads to be in read mode, allows one thread to be in write mode with exclusive ownership of the lock, and allows one thread that has read access to be in upgradeable read mode, from which the thread can upgrade to write mode without having to relinquish its read access to the resource.


简而言之:


ReaderWriterLockSlim提供对某资源在某时刻下的多线程同读 或 单线程独占写。

此外,ReaderWriterLockSlim还提供从读模式无缝升级到独占写模式。


总结下来:


读写锁处于以下四种状态:


1.未进入: 没有线程进入锁(或者所有线程退出锁)


2.读模式:每次调用EnterReadlock时,锁计数都会增加,但允许您读取其中的代码块。


3.写模式:独占、排他


4.可升级的读模式(upgradeable read mode):多线程读,其中一个线程具备在某时刻升级到排他写模式的可能。


btw,读写锁相比常规lock之外,还具备锁超时的机制,能避免未知原因持续占有锁导致的死锁。


这就很适合我们开发DAL.Connection组件的多读少写的场景。


微软ReaderWriterLockSlim页面还很贴心的给了一个基于读写锁的缓存操作封装类SynchronizedCache


开箱即用的缓存操作类


基于ReaderWriterLockSlim对线程不安全的Dictionary进行了包装, 可以作为一个多读少写的缓存操作类。


public class SynchronizedCache 
{
    private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    private Dictionary<int, string> innerCache = new Dictionary<int, string>();
    public int Count
    { get { return innerCache.Count; } }
    public string Read(int key)
    {
        cacheLock.EnterReadLock();
        try
        {
            return innerCache[key];
        }
        finally
        {
            cacheLock.ExitReadLock();
        }
    }
    public void Add(int key, string value)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Add(key, value);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }
    public bool AddWithTimeout(int key, string value, int timeout)
    {
        if (cacheLock.TryEnterWriteLock(timeout))
        {
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
            return true;
        }
        else
        {
            return false;
        }
    }
    public AddOrUpdateStatus AddOrUpdate(int key, string value)
    {
        cacheLock.EnterUpgradeableReadLock();
        try
        {
            string result = null;
            if (innerCache.TryGetValue(key, out result))
            {
                if (result == value)
                {
                    return AddOrUpdateStatus.Unchanged;
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache[key] = value;
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Updated;
                }
            }
            else
            {
                cacheLock.EnterWriteLock();
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return AddOrUpdateStatus.Added;
            }
        }
        finally
        {
            cacheLock.ExitUpgradeableReadLock();
        }
    }
    public void Delete(int key)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Remove(key);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }
    public enum AddOrUpdateStatus
    {
        Added,
        Updated,
        Unchanged
    };
    ~SynchronizedCache()
    {
       if (cacheLock != null) cacheLock.Dispose();
    }
}


缓存操作类SynchronizedCache每次操作会返回操作结果,和常见的字典一样,不带值变更通知的能力,我们还是像面试官:实现一个带值变更通知能力的Dictionary 一文那样,添加值变更事件,注册变更逻辑


public event EventHandler<ValueChangedEventArgs<string>> OnValueChanged;
//--- 节选自AddOrUpdate方法
cacheLock.EnterWriteLock();
try
{
   OnValueChanged?.Invoke(this, new ValueChangedEventArgs<string>(key));
   innerCache[key] = value;
}
finally
{
    cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
//---
if (sc.AddOrUpdate(key, value) == SynchronizedCache.AddOrUpdateStatus.Updated)
{
    Console.WriteLine($"已经发生了值变更,原key对应的键值已经被重写。");}
}


输出旁白


本文记录了读写锁在日常开发中的实践,大多数场景都是多读少写,读者可以思考一下是不是也可以将项目中的无脑lock替换为SynchronizedCache


文是同程艺龙DAL.Connection组件研发过程的一个小插曲,有心的读者可以往上翻一翻,了解上下文背景、了解小码甲的思考过程。


这就像我们高中做数学题,直接看答案并不能快速提升,结合上下文自然、流畅的转到这个方向才是最重要的。

相关文章
|
1月前
|
存储 安全 Java
每日大厂面试题大汇总 —— 今日的是“美团-后端开发-一面”
文章汇总了美团后端开发一面的面试题目,内容涉及哈希表、HashMap、二叉树遍历、数据库索引、死锁、事务隔离级别、Java对象相等性、多态、线程池拒绝策略、CAS、设计模式、Spring事务传播机制及RPC序列化工具等。
43 0
|
1月前
|
存储 消息中间件 NoSQL
每日大厂面试题大汇总 —— 今日的是“京东-后端开发-一面”
文章汇总了京东后端开发一面的面试题目,包括ArrayList与LinkedList的区别、HashMap的数据结构和操作、线程安全问题、线程池参数、MySQL存储引擎、Redis性能和线程模型、分布式锁处理、HTTP与HTTPS、Kafka等方面的问题。
105 0
|
1月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
2月前
|
消息中间件 安全 前端开发
面试官:单核服务器可以不加锁吗?
面试官:单核服务器可以不加锁吗?
50 4
面试官:单核服务器可以不加锁吗?
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
1月前
|
SQL 安全 关系型数据库
第三次面试总结 - 吉云集团 - 全栈开发
本文是作者对吉云集团全栈开发岗位的第三次面试总结,面试结果非常好,内容全面覆盖了Java基础、MySQL和项目经验,作者认为自己的MySQL基础知识稍弱,需要加强。
32 0
第三次面试总结 - 吉云集团 - 全栈开发
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
1月前
|
消息中间件 存储 前端开发
资深Android开发的5个经典面试题
本文首发于公众号“AntDream”,欢迎关注。文章详细解答了五个常见的Android面试题,涵盖内存泄漏与溢出、Binder机制、MVC/MVP/MVVM架构、Handler机制及Context对象等内容,帮助读者深入了解Android开发的核心概念。
33 0
|
1月前
|
NoSQL 前端开发 关系型数据库
第四次面试总结 — 嘉和智能 - 全栈开发
本文是作者对嘉和智能全栈开发岗位的第四次面试总结,主要围绕对各种技术栈的了解程度进行提问,包括数据库的使用经验、对Redis和nginx的理解以及前端技能水平。
18 0
|
1月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
29 0