2010-11-17 23 views
0

前段时间我使用软引用创建了一个缓存,但是在试图解决一个错误时,我担心实际上我做了不正确的操作,并且在它不应该删除对象时,吨。这就是我已经做到了:这是使用软参考的正确方法

private static final Map<String, SoftReference<Buffered>> imageMap = 
     new HashMap<String,SoftReference<Buffered>>(); 

public static synchronized Buffered addImage(String sum, final byte[] imageData) 
    { 
     SoftReference<Buffered> bufferedRef = imageMap.get(sum); 
     Buffered buffered; 
     if (bufferedRef!=null) 
     { 
      //There are no longer any hard refs but we need again so add back in 
      if(bufferedRef.get()==null) 
      { 
       buffered = new Buffered(imageData, sum); 
       imageMap.put(sum, new SoftReference(buffered)); 
      } 
      else 
      { 
       buffered=bufferedRef.get(); 
      } 
     } 
     else 
     { 
      buffered = new Buffered(imageData, logDescriptor, sum); 
      imageMap.put(sum, new SoftReference(buffered)); 
     } 
     return buffered; 
    } 

    public static Buffered getImage(String sum) 
{   
    SoftReference<Buffered> sr = imageMap.get(sum); 
    if(sr!=null) 
    { 
     return sr.get(); 
    } 
    return null; 
} 

这样的想法是调用进程可以添加可identifed新的缓冲对象/抬头通过密钥总和,那么只要这个缓冲对象正在使用通过至少一个对象它不会被从地图中移除,但是如果它不再被任何对象使用,那么如果内存变得紧张,它可能是垃圾收集。

但在我的代码现在期待的是,在关键领域的总和总是被引用别的地方最重要的事情(这是不一定的情况下)

编辑:所以,我想Colin的解决方案,但我是那种因为putIfAbsent()似乎没有返回附加值,所以难倒了。我修改了addImage方法来获取一些调试

public static synchronized Buffered addImage(String sum, final byte[] imageData) 
    { 
     Buffered buffered = new Buffered(imageData, sum); 
     Buffered buffered2 = imageMap.get(sum); 
     Buffered buffered3 = imageMap.putIfAbsent(sum,buffered); 
     Buffered buffered4 = imageMap.get(sum); 
     System.out.println("Buffered AddImage1:"+buffered); 
     System.out.println("Buffered AddImage2:"+buffered2); 
     System.out.println("Buffered AddImage3:"+buffered3); 
     System.out.println("Buffered AddImage4:"+buffered4);     
     return buffered2; 
    } 

回报

Buffered AddImage1:[email protected] 
Buffered AddImage2:null 
Buffered AddImage3:null 
Buffered AddImage4:[email protected] 

所以这清楚地表明缓冲的情况下是不存在下手,并成功构建了补充,但肯定应该返回通过putIfAbsent?

+0

究竟是什么问题? 此外,getImage也应该同步,并且代码中存在内存泄漏(所收集引用的条目未被释放)。 – Kru 2010-11-17 21:27:05

+0

Chris:我的问题是可以删除一个Buffered类的实例,但仍然在我的代码中引用某处,我不明白你的内存泄漏点。 – 2010-11-17 22:35:43

回答

2

我推荐使用GuavaMapMaker而不是自己做这个。

private static final ConcurrentMap<String, Buffered> imageMap = 
    new MapMaker().softValues().makeMap(); 

public static Buffered addImage(String sum, final byte[] imageData) { 
    Buffered buffered = new Buffered(imageData, sum); 
    Buffered inMap = imageMap.putIfAbsent(sum, buffered); 
    return inMap != null ? inMap : buffered; 
} 

public static Buffered getImage(String sum) {   
    return imageMap.get(sum); 
} 

由于这是一个ConcurrentMap并使用putIfAbsent,你不必同步addImage除非创造Buffered一个实例是昂贵的。这与处理代码时不同,它实际上处理的是从地图中删除条目,当它们的值被垃圾收集时。

编辑:如果你打电话getImage并获得null结果(也许是因为该值被垃圾回收),你会怎么做?有什么方法可以根据sum键获取图像数据byte[]?如果是这样,您可能希望将给定sumBuffered实例创建为Function<String, Buffered>。这使您可以使用计算映射而不是一个正常的:

private static final ConcurrentMap<String, Buffered> imageMap = new MapMaker() 
    .softValues() 
    .createComputingMap(getBufferedForSumFunction()); 

使用这种方式时,你可能甚至不需要addImage方法...如果在地图上调用get并且它没有给定sum的条目,它将调用该函数,缓存结果并返回它。

+0

:感谢您的回答,创建Buffered实例是'昂贵'的,因为它需要时间并写入数据库,所以假设需要同步,并且不能从密钥中重新生成。我会尝试Guava,因为我开始将它用于别的东西,但是我想知道我的代码实际上是否是错误的,因为某些类中引用的Buffered实例可能会使用我现有的代码删除。 – 2010-11-17 22:27:41

+0

..有没有一种方法可以跟踪何时引用实际上是通过gc从地图中删除的(用我的代码或Guava代码) – 2010-11-17 22:31:45

+0

@ user294896:是的,您可以在构建地图时调用'evictionListener(MapEvictionListener )'设置一个侦听器在一个条目被驱逐后被异步调用。在地图上调用任何'public'方法时,监听器都会被调用。 – ColinD 2010-11-17 22:51:36

1

如果您只想让数据在未被引用的地方进行垃圾收集,请使用WeakHashMap。

如果您希望地图能够在数据不再可用时重新创建数据,那么您需要修改getImage()以检查引用是否可用,如果不可用,请重新创建它。

在我看来,你想要的是前者。

软引用和弱引用之间的区别在于垃圾回收器使用算法来决定是否要回收可轻松访问的对象,但始终回收弱可达对象。 (ref)

+2

在'WeakHashMap'中,键被弱引用,值是普通引用。 – Kru 2010-11-17 21:22:58

+3

在大多数情况下,SoftReference比缓存中的WeakReferences更适合:只要没有更强的引用,WeakReference可能会被启用。在这种情况下,除非JVM在内存上运行不足,否则SoftReferences不会考虑GCing,除非JVM在内存上运行不足 – 2010-11-17 21:39:32

相关问题