当前位置: 首页 > news >正文

使用ConcurrentMap实现高效可靠的原子操作

问题:服务器S1从远程获取多个文件到本地处理。这些文件的数据会被Processor转换成不同类型的数据模型存放至S1的数据库。每个Processor处理逻辑是相互独立的,但是同一个文件的数据可能会被多个Processor访问。为了提高数据模型的转换效率,需要将文件进行缓存,已经被缓存的文件不能被重复缓存。问题代码如下:

[java] view plain copy
public class CacheManager
{

private Collection<String> cachedFiles = new ArrayList<>();  

public void tryCache(String file)  
{  
    if (!cachedFiles.contains(file))  
    {  
        doCache(file);  
        cachedFiles.add(file);  
    }  
}  

}

在多线程环境下,tryCache方法的逻辑会存在由“线程交织”所带来的文件被重复缓存的问题。

以下几种解决方法点评一下:

1 一种错误的方法

将cachedFiles字段使用诸如ConcurrentSkipListSet 或Collections.synchronizedSet,但是,该方法根本解决不了“线程交织”的问题。部分Java初学者容易犯这种错误。

2 一种不高效的方法

将tryCache声明为synchronized方法,或者在if (!cachedFiles.contains(file))语句块外用synchronized(cachedFiles),来实现互斥。这方法能保证if (!cachedFiles.contains(file))块在任何时候只能被一个线程执行,的确能避免文件被重复缓存。但是性能不高,例如,如果Processor1要缓存文件A,Processor2要缓存文件B,两者并不冲突,但是两个Processor只能串行通过tryCache,却不能同时进行。

3 一种高效,但不可靠的方法

使用ConcurrentMap的putIfAbsent实现高效的原子操作,但不可靠

经过改造的伪代码如下:

[java] view plain copy
public class CacheManager
{

private Collection<String> cachedFiles = new HashSet<>();  
private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();  
public void tryCache(String file)  
{  
    do  
    {  
        if (cacheTimestamp.putIfAbsent(file, System.currentTimeMillis()) == null)  
        {  
            if (!cachedFiles.contains(file))    
            {    
                doCache(file);    
                cachedFiles.add(file);    
            }   
            cacheTimestamp.remove(file);    
            break;  
        }  
        else  
        {  
            waitSomeTime();  
        }  
    }  
    while(!Thread.interrupted());  
}  

}
该方法能保证同一个file不会同时被多个Processor进行缓存,而且也能让处理不同file的Processor并发进行缓存。

但是该方法却并不可靠:如果Processor1在doCache(fileA)时发生异常导致cacheTimestamp.remove(fileA)不被执行,那么再也不会有其他Processor能通过cacheTimestamp.putIfAbsent(fileA, System.currentTimeMillis()) == null的校验,使得fileA永远不会再被缓存。cacheTimestamp也留下了一个垃圾记录。

4 一种高效,基本可靠的方法

[java] view plain copy
public class CacheManager
{

private Collection<String> cachedFiles = new HashSet<>();  
private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();  
public void tryCache(String file)  
{  
    do  
    {  
        if (cacheTimestamp.putIfAbsent(file, System.currentTimeMillis()) == null)  
        {  
            try    
            {    
                if (!cachedFiles.contains(file))    
                {    
                    doCache(file);    
                    cachedFiles.add(file);    
                }   
                break;  
            }    
            finally    
            {    
                cacheTimestamp.remove(file);    
            }  
        }  
        else  
        {  
            waitSomeTime();  
        }  
    }  
    while(!Thread.interrupted());  
}  

}

使用try ... finally ...来保证无论缓存成功与否,都能将cacheTimestamp中的记录清除。至此,这代码可以给个及格分了。什么,才及格?是的,请继续往下看

5 一种高效,靠超时机制来保证可靠性的方法

使用ConcurrentMap的putIfAbsent和replace方法,能实现上述问题的一种高效而可靠的解决方案。

[java] view plain copy
public class CacheManager
{

private Collection<String> cachedFiles = new HashSet<>();  

private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();  
  
private final long TIMEOUT = 600000;  

public void tryCache(String file)  
{  
    do  
    {  
        Long timestamp = cacheTimestamp.putIfAbsent(file, System.currentTimeMillis() + TIMEOUT)  
        if (timestamp == null)  
        {  

[java] view plain copy

        timestamp = <span style="font-family: Arial, Helvetica, sans-serif;">cacheTimestamp.get(file);</span>  

        try    
        {    
            if (!cachedFiles.contains(file))    
            {    
                doCache(file);    
                cachedFiles.add(file);    
            }   
            break;  
        }    
        finally    
        {    
            cacheTimestamp.remove(file, <span style="font-family: Arial, Helvetica, sans-serif;">timestamp</span><span style="font-family: Arial, Helvetica, sans-serif;">);  </span>  
        }  
    }  
    else if (System.currentTimeMillis() > timestamp) // 缓存file超时  
    {  
        if(cacheTimestamp.replace(file, timestamp, System.currentTimeMillis() + TIMEOUT))  
        {  
            try    
            {    
                if (!cachedFiles.contains(file))    
                {    
                    doCache(file);    
                    cachedFiles.add(file);    
                }   
                break;  
            }    
            finally    
            {    
                cacheTimestamp.remove(file, <span style="font-family: Arial, Helvetica, sans-serif;">timestamp</span><span style="font-family: Arial, Helvetica, sans-serif;">);  </span>  
            }  
        }  
    }  
    else  
    {  
        wait(timestamp - System.currentTimeMillis());  
    }  
}  
while(!Thread.interrupted());  

}

6 一种高效,靠等待机制来保证可靠性的方法

使用ConcurrentMap的putIfAbsent方法和CountDownLatch对象,能实现上述问题的另一种高效而可靠的解决方案。

[java] view plain copy
public class CacheManager
{

private Collection<String> cachedFiles = new HashSet<>();  

private ConcurrentMap<String, CountDownLatch> cacheTimestamp = new ConcurrentHashMap<>();  

public void tryCache(String file)  
{  
    CountDownLatch signal = cacheTimestamp.putIfAbsent(file, new CountDownLatch(1))  
    if (signal == null)  
    {  
        signal = cacheTimestamp.get(file);  
        try    
        {    
            if (!cachedFiles.contains(file))    
            {    
                doCache(file);    
                cachedFiles.add(file);    
            }   
            break;  
        }    
        finally    
        {    
            signal.countDown();   
            cacheTimestamp.remove(file);  
        }  
    }  
    else  
    {  
        signal.await();  
    }  
}  

}

相关文章:

  • 岂止于大:大数据这个词已经过时了
  • mysql基本命令
  • 雅虎发布开源Web应用安全扫描器Gryffin
  • 正则表达式小知识点
  • 史上最贵的12个域名 360排名第一
  • 记生产服务器频繁死机重大事故
  • 《认知设计:提升学习体验的艺术》——学习者的情境
  • C++ 编译错误 jump to case label [-fpermissive]
  • PHP的Ev教程三(Periodic watcher)
  • 雇佣兵
  • mongoDB伪副本集集群搭建
  • 《PHP精粹:编写高效PHP代码》——2.6节高级PDO特征
  • 影响数据中心托管的因素
  • 中兴视觉大数据报道:人工智能应用将会帮助我们更好的决策
  • 如何使用Shodan搜索引擎来诊断漏洞?
  • Docker入门(二) - Dockerfile
  • laravel 用artisan创建自己的模板
  • Linux链接文件
  • MySQL QA
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Python学习之路13-记分
  • Redis在Web项目中的应用与实践
  • SpiderData 2019年2月13日 DApp数据排行榜
  • SpringBoot 实战 (三) | 配置文件详解
  • vuex 笔记整理
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 检测对象或数组
  • 设计模式 开闭原则
  • 听说你叫Java(二)–Servlet请求
  • 我有几个粽子,和一个故事
  • 栈实现走出迷宫(C++)
  • Spring Batch JSON 支持
  • 如何在招聘中考核.NET架构师
  • ​决定德拉瓦州地区版图的关键历史事件
  • #HarmonyOS:基础语法
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (定时器/计数器)中断系统(详解与使用)
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (三)docker:Dockerfile构建容器运行jar包
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (算法设计与分析)第一章算法概述-习题
  • (一)kafka实战——kafka源码编译启动
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (转) Face-Resources
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .NET 中让 Task 支持带超时的异步等待
  • .net专家(高海东的专栏)
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • [Ariticle] 厚黑之道 一 小狐狸听故事
  • [C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子
  • [Docker]五.Docker中Dockerfile详解