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

简介: java 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();  
    }  
}  

}

目录
相关文章
|
1月前
|
缓存 安全 Java
保障线程安全性:构建可靠的多线程应用
保障线程安全性:构建可靠的多线程应用
|
3月前
|
算法 安全 编译器
并发的三大特性
并发的三大特性
38 1
|
3月前
|
存储 NoSQL Redis
单线程模型想象不到的高并发能力、多路复用是效率杠杆
单线程模型想象不到的高并发能力、多路复用是效率杠杆
|
6月前
|
存储 Linux 调度
确保并发执行的安全性:探索多线程和锁机制以构建可靠的程序
在当今计算机系统中,多线程编程已成为常见的需求,然而,同时也带来了并发执行的挑战。为了避免数据竞争和其他并发问题,正确使用适当的锁机制是至关重要的。通过阅读本文,读者将了解到多线程和锁机制在并发编程中的重要性,以及如何避免常见的并发问题,确保程序的安全性和可靠性。通过实际案例和代码示例来说明如何正确地使用多线程和锁机制来构建可靠的程序。
20 1
|
17天前
|
存储 缓存 安全
【企业级理解】高效并发之Java内存模型
【企业级理解】高效并发之Java内存模型
|
18天前
|
存储 负载均衡 并行计算
实现优雅并行编程:确保正确性与提升性能的关键要素
在程序开发中,并行编程一种利用多个处理器或计算资源同时执行多个任务的编程方式,它能够提高计算效率和性能,是提高计算效率和性能的关键手段,但它也带来了一系列复杂的问题,涉及到任务分解、数据同步、资源分配等诸多复杂问题,稍有不慎就可能导致性能瓶颈、死锁甚至数据不一致等状况。编写优雅的并行程序需要在保证程序正确性的前提下,实现高效的并行计算。那么本文就来探讨一下如何在保证程序正确性的前提下,实现优雅的并行程序,以提升计算效率和性能,包括任务分解、数据同步和资源分配等方面的关键要素,希望能够为读者提供一些有用的指导和启示。
16 2
实现优雅并行编程:确保正确性与提升性能的关键要素
|
5月前
|
缓存 NoSQL 应用服务中间件
零拷贝并非万能解决方案:重新定义数据传输的效率极限
本文讨论了零拷贝在优化数据传输效率方面的局限性。尽管零拷贝技术在减少数据传输过程中的内存拷贝次数方面有很大的优势,但它并非适用于所有情况。文章介绍了一些其他的优化方法,如异步I/O和直接I/O的组合、根据文件大小选择不同的优化方式。至此,我们的计算机基础专栏就结束了,不知道大家有没有发现,操作系统底层提供了丰富的解决方案来支持应用程序的复杂性和可扩展性。对于任何工作中遇到的问题,我们都可以从操作系统的角度寻找解决方法。
零拷贝并非万能解决方案:重新定义数据传输的效率极限
|
5月前
|
消息中间件 存储 缓存
详解高性能无锁队列的实现-1
详解高性能无锁队列的实现
134 0
|
5月前
|
算法 安全 NoSQL
详解高性能无锁队列的实现-2
详解高性能无锁队列的实现
41 0
|
6月前
|
Java
并发三大特性
并发三大特性
20 0