这是使用软参考的正确方法吗?

时间:2010-11-17 20:55:02

标签: java caching soft-references

我前一段时间使用Soft References创建了一个缓存,但是在尝试解决一个bug时,我开始担心实际上我已经错误地完成了它并且当它不应该删除它时它会移除它。这就是我做到的:

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;
}

所以这个想法是一个调用进程可以添加新的Buffered对象,可以通过键和来识别/查找,然后只要这个Buffered对象被至少一个对象使用它就不会被删除。 map,但如果它不再被任何对象使用,那么如果内存变紧,它可能是垃圾收集。

但现在查看我的代码是重要的事情,关键字段总和总是被引用到其他地方(不一定是这种情况)

编辑:所以我尝试了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:com.Buffered@6ef725a6
Buffered AddImage2:null
Buffered AddImage3:null
Buffered AddImage4:com.Buffered@6ef725a6

所以它清楚地表明Buffered实例不是从那里开始并且已成功构建和添加,但肯定应该由putIfAbsent返回?

2 个答案:

答案 0 :(得分: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(可能是因为该值是垃圾回收),您会怎么做?是否有某种方法可以根据byte[]键获取图像数据sum?如果是这样,您可能希望将为Buffered创建sum实例的过程封装为Function<String, Buffered>。这允许您使用计算地图而不是正常地图:

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

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

答案 1 :(得分:1)

如果您想要做的只是允许数据在任何地方都没有被引用时收集垃圾,请使用WeakHashMap。

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

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

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