• ReentrantReadWriteLock读写锁实现分析


      排他锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程均被阻塞。读写锁内部维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大的提升。

      读写锁除了保证写操作对读操作的可见性和提高并发的性能之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个用作缓存的共享的数据结构,它的大部分时间提供读取服务,而写操作占有的时间非常的少,但是写操作完成之后的更新需要对后续的读服务可见。在没有读写锁支持的时候(JAVA 5之前),要完成上述工作就要使用Java的等待/通知机制,就是当写操作开始的时候,所有晚于写操作的读操作均会进入等待状态,只有写操作完成并进行通知之后,所有等待的读操作才能继续执行(写操作之间依靠synchronized进行同步),这样做的目的是使读操作能读取到正确的数据,不会出现脏读。改用读写锁实现上述功能,只需要在读操作时候获取读锁,写操作时候获取写锁,当写锁被获取到时,后续的(非当前写操作线程)所有读写操作都会被阻塞,在写锁释放之后,所有操作继续执行,编程方式相对于使用等待通知机制的实现方式变得简单明了。

      Java并发包提供读写锁的实现是ReentrantReadWriteLock,它提供如下特性:

      (1)公平性选择,支持非公平锁和公平锁的获取方式。

      (2)重进入,支持重进入,线程获取读锁之后该线程能继续获取读锁;线程获取写锁之后能继续获取写锁,也可以获取读锁。

      (3)锁降级,遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁。

      下面通过一个缓存示例说明读写锁的使用方式,示例代码如下:

    public class Cache {
        private static final Map<String,Object> cache = new HashMap<String,Object>() ;
        private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock() ;
        private static Lock r = lock.readLock() ;
        private static Lock w = lock.writeLock();
        
        public static final Object get(String key){
            r.lock();
            try {
                return cache.get(key) ;
            }finally{
                r.unlock(); 
            }
        }
        
        public static final Object put(String key,Object val){
            w.lock();
            try{
                 return  cache.put(key, val);
                
            }finally{
                w.unlock();
            }
            
        }
        
        public static final void clear(){
            w.lock();
            try{
                cache.clear();
                
            }finally{
                w.unlock();
            }
        }
        
        public static final Object remove(String key){
            w.lock();
            try{
                 return  cache.remove(key) ;
                
            }finally{
                w.unlock();
            }
        }
    }

      上述的Cache组合一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证Cache是线程安全的。在读操作get(String key)的方法中,需要获取读锁,这使得所有的读并发都不会被阻塞。写操作put(String key,Object val)方法和clear()和remove(String key)方法,在更新HashMap时必须提前获取写锁,当获取写锁之后,其他线程对于写锁和读锁的获取都将会被阻塞,而只有写锁释放之后其他读写操作才能够继续。Cache使用读写锁提升了读并发的性能,也保证了每次写操作对所有的读写操作的可见性,同时简化了编程方式。

      读写锁同样依赖自定义同步器实现同步功能,而读写的状态就是同步器的同步状态。因为读写锁的特性,所以读写锁需要在同步状态上维护多个读线程和一个写线程的状态,所以该状态的设计成为了实现读写锁的关键。ReentrantReadWriteLock是将一个int变量按位切割分成两部分维读与写状态,高16位表示读,低16位表示写,如下图:

     

      上图表示一个线程已经获取了写锁,并且重进入了一次,同时也连续获取了3次读锁。读写锁通过位运算能够迅速的写各自的状态,假设当前同步状态的值是S ,写状态等于S & 0x0000ffff把高16位全部抹去得到写锁的状态,读状态等于S >>>16(无符号补0右移16位),当写状态加1时,等于S+1;读状态加1 时,等于S+(1 << 16),也就是 S+ 0x00010000 .

      

      

      

  • 相关阅读:
    代码重构与单元测试 ---- 系列文章
    在.Net Core中,把HttpClient换成IHttpClientFactory之使用技巧
    ABP框架使用Oracle数据库,并实现从SQLServer中进行数据迁移的处理
    总结开发中基于DevExpress的Winform界面效果
    Winform框架中窗体基类的用户身份信息的缓存和提取
    循序渐进BootstrapVue,开发公司门户网站(6)--- 门户网站后端内容管理
    元学习MAML——要解决的问题是给你一堆猫狗图片(训练样本较多),然后给你一类黑天鹅图谱(样本少),让你训练一个模型,能够泛化能力好,识别猫狗和黑天鹅
    nmap -A 启用操作系统和版本检测,脚本扫描和路由跟踪功能
    安全数据集汇总——from安全学术圈 https://secdr.org/
    DNS域传送漏洞——由于DNS服务器配置不当,可能导致攻击者获取某个域(域名)的所有(子域名)记录
  • 原文地址:https://www.cnblogs.com/dquery/p/7065723.html
Copyright © 2020-2023  润新知