zoukankan      html  css  js  c++  java
  • 看看这个Lock可不可靠 CQ

    由于业务的需要,设计了一个Lock。

    这个Lock的设计要求如下:

    1. 数据被多线程访问
    2. 对数据的访问分为读和写
    3. 当任一线程读数据时,其它线程不能写数据
    4. 当任一线程写数据时,其它线程不能写数据,其它线程不能读数据
    5. 由于读数据的频率远远高于写数据的频率,所以读数据线程的优先级更高
    6. 不允许死锁的情况发生

    这里实际上要求的是读和写的互斥,写和写之间的互斥,但读与读之间并不互斥。显然,不能用一个简单的锁来搞定。例如下面的代码

    class DataService<T>
        {
            List<T> mCache = new List<T>();
            private object mLockObject = new object();
    
            public List<T> Search(Predicate<T> predicate)
            {
                lock (mLockObject)
                {
                    return mCache.FindAll(predicate);
                }
            }
    
            public void Upate(int key)
            {
                lock (mLockObject)
                { 
                    //somemethod to update mCache with key
                }
            }
        }

    这段代码确实是实现了读写互斥,写与写的互斥,但是读与读之间也发生了互斥。由于读的频率很高,所以读与读之间的互斥会直接影响到系统的性能。

    为此,我设计了2个Lock

    private object mWriteLock = new object();
                private object mReadLock = new object();

    写与写的互斥

    mWriteLock用于独占写入,但它仅仅控制的是写操作,对读操作没有影响。

    public void LockToWrite(Action action)
                {
                    lock (mWriteLock)
                    {
                        action();
                    }
                }
    

    读与读之间不互斥

    mReadLock用于读操作,但它并不直接锁定读操作本身,而是锁定读操作的计数。所以,和mReadLock一同工作的还有一个数据

    private int mReadSeed = 0;

    mReadLock实际上是用于锁定mReadSeed的读写。

    private void IncrementReadSeeds()
                {
                    lock (mReadLock)
                        mReadSeed++;
                }
                private void DecrementReadSeeds()
                {
                    lock (mReadLock)
                        mReadSeed--;
                }

    当读操作发生时,调用以上方法

    public T LockToRead<T>(Func<T> action)
                {
                    try
                    {
                        IncrementReadSeeds();
                            
                        return action();
                    }
                    finally
                    {
                        DecrementReadSeeds();
                    }
                }

    在读操作中,由于只是锁定的mReadSeeds的运算,而不是锁定的action运算,所以即便是action的运行时间会很长,也不会阻止其它线程的读操作。

    其实这里暴露了一个问题,因为锁定的只是所谓的读操作,而锁定的数据却根本没有提及,即没有办法从代码上保证,action就不会对真正要保护的资源进行写操作。还好,这个类只会作为DataService类中的一个子类存在,这样就可以通过约定来解决上面的顾虑(目前水平有限,也只能出此下策)

    读与写之间的互斥

    读与写之间的互斥,需要将两个锁关联起来。这段代码很关键。

    public void WaitToWrite()
                {
                    while (true)
                    {
                        lock (mReadLock)
                        {
                            if (mReadSeed == 0)
                            {
                                mCanRead = false;
                                break;
                            }
                        }
                        Thread.Sleep(1);
                    }
                }

    在WaitToWrite方法中,使用了mReadLock,当mReadSeed==0是,将mCanRead标记为false。

    在LockToRead方法中,我们可以看到,只有当所有的读操作都完成后,mReadSeed才可能为0。而当所有的读操作完成后,WaitToWrite方法将mCanRead置为false。而这个操作受到了mReadLock的保护。

    于是读的代码要稍作调整

    public T LockToRead<T>(Func<T> action)
                {
                    try
                    {
                        while (true)
                        {
                            WaitToRead();
                            IncrementReadSeeds();
                            if (!mCanRead)//因为IncrementReadSeeds和WaitToWrite都使用了mReadLock,所以我认为这里读取mCanRead是安全的,这是这个算法的关键,不知道大家是否这么看?
                            {
                                DecrementReadSeeds();
                                continue;
                            }
                            else
                                break;
                        }
    
                        return action();
                    }
                    finally
                    {
                        DecrementReadSeeds();
                    }
                }

    在上面的代码中调用了WaitToRead()方法,它实际上是用来判断当前的读操作是否被允许的第一道关卡。

    WaitToWrite同样被置于mWriteLock的保护之下,那同样意味着mCanRead=false操作受到了mWriteLock的保护。写的代码调整为

    public T LockToWrite<T>(Func<T> action)
                {
                    lock (mWriteLock)
                    {
                        WaitToWrite();//等待读操作完成
    
                        try
                        {
                            return action();
                        }
                        finally
                        {
                            mCanRead = true;//将允许读的操作置为true
                        }
                    }
                }

    是否会出现读写互锁

    我认为问题的关键在于mReadSeeds==0的状态与mCanRead的状态处理上。

    mReadSeeds的处理依赖于mReadLock,mCanRead置为false的处理也依赖于mReadLock的保护,所以IncrementReadSeeds();if(!mCanRead)也受到了mReadLock的保护。

    而WatiToWrite(那么将mCanRead置为false的方法)受到了mWriteLock的保护,所以mCanRead对于写的操作是安全的。

    因为mReadSeeds==0并不受到写操作的影响,所以不会出现读写互锁的情况。

    以下是LockClass的全部代码,还望大家多多指教。

    另外,感谢msolap的建议。我会尝试用他的建议来优化代码。

    class LockClass
            {
                private bool mCanRead = false;
                private object mWriteLock = new object();
                private object mReadLock = new object();
                private int mReadSeed = 0;
    
    
                private void WaitToWrite()
                {
                    while (true)
                    {
                        lock (mReadLock)
                        {
                            if (mReadSeed == 0)
                            {
                                mCanRead = false;
                                break;
                            }
                        }
                        Thread.Sleep(1);
                    }
                }
    
                public void LockToWrite(Action action)
                {
                    lock (mWriteLock)
                    {
                        WaitToWrite();
    
                        try
                        {
                            action();
                        }
                        finally
                        {
                            mCanRead = true;
                        }
                    }
                }
    
                
                private void IncrementReadSeeds()
                {
                    lock (mReadLock)
                        mReadSeed++;
                }
                private void DecrementReadSeeds()
                {
                    lock (mReadLock)
                        mReadSeed--;
                }
    
                public T LockToRead<T>(Func<T> action)
                {
                    try
                    {
                        while (true)
                        {
                            WaitToRead();
                            IncrementReadSeeds();
                            if (!mCanRead)
                            {
                                DecrementReadSeeds();
                                continue;
                            }
                            else
                                break;
                        }
    
                        return action();
                    }
                    finally
                    {
                        DecrementReadSeeds();
                    }
                }
    
                private void WaitToRead()
                {
                    while (!mCanRead)
                    {
                        Thread.Sleep(1);
                    }
                }
    
            }
  • 相关阅读:
    java OA系统 自定义表单 流程审批 电子印章 手写文字识别 电子签名 即时通讯
    flowable 获取当前任务流程图片的输入流
    最新 接口api插件 Swagger3 更新配置详解
    springboot 集成 activiti 流程引擎
    java 在线考试系统源码 springboot 在线教育 视频直播功能 支持手机端
    阿里 Nacos 注册中心 配置启动说明
    springboot 集成外部tomcat war包部署方式
    java 监听 redis 过期事件
    springcloudalibaba 组件版本关系
    java WebSocket 即时通讯配置使用说明
  • 原文地址:https://www.cnblogs.com/czy/p/1659552.html
Copyright © 2011-2022 走看看