在guava缓存api中的RemovalListener回调是否确保没有人使用该对象

Aru*_*kar 4 java caching guava

我在维基页面上阅读了有关缓存的代码示例/文档.我看到回调RemovalListener可以用来拆除被驱逐的缓存对象.我的问题是库在确保调用提供的对象之前没有被任何其他线程使用RemovalListener.让我们考虑一下docs中的代码示例:

CacheLoader<Key, DatabaseConnection> loader = 
                                 new CacheLoader<Key, DatabaseConnection> () {
  public DatabaseConnection load(Key key) throws Exception {
    return openConnection(key);
  }
};
RemovalListener<Key, DatabaseConnection> removalListener =
                          new RemovalListener<Key, DatabaseConnection>() {
  public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
    DatabaseConnection conn = removal.getValue();
    conn.close(); // tear down properly
  }
};

return CacheBuilder.newBuilder()
  .expireAfterWrite(2, TimeUnit.MINUTES)
  .removalListener(removalListener)
  .build(loader);
Run Code Online (Sandbox Code Playgroud)

这里缓存被配置为在创建后2分钟驱逐元素(我知道它可能不是精确的两分钟,因为驱逐将与用户读/写调用等一起捎带)但是不管时间是什么,库是否会检查那里没有活动的引用呈现给传递给的对象RemovalListener?因为我可能有另一个线程长时间从缓存中获取对象但可能仍在使用它.在这种情况下,我无法close()从RemovalListener 调用它.

此外,文档RemovalNotification说明:删除单个条目的通知.如果密钥和/或值已经被垃圾收集,则它们可以为null. 所以根据它conn可以null在上面的例子中.在这种情况下,我们如何正确拆除conn物体?在这种情况下,上面的代码示例也将抛出NullPointerException.

我试图解决的用例是:

  1. 缓存元素需要在创建两分钟后过期.
  2. 被驱逐的物体需要closed,但只有在确定没有人使用它们之后.

Lou*_*man 8

番石榴贡献者在这里.

我的问题是库在调用提供的RemovalListener之前确保该对象没有被任何其他线程使用.

不,这对番石榴来说通常不可能做到 - 而且反正一个坏主意!如果缓存值是Integers,那么因为Integer.valueOf重用Integer128以下的整数对象,你永远不会使值低于128的条目到期.这将是不好的.

此外,RemovalNotification的文档说明:删除单个条目的通知.如果密钥和/或值已经被垃圾收集,则它们可以为null.所以根据它,conn在上面的例子中可能为null.

要清楚,只有在你使用,,或者是weakKeys,才有可能.(而且,正如你已经正确推断的那样,如果你需要对价值进行一些拆解,你就不能真正使用其中任何一种.)如果你只是使用其他形式的过期,你永远不会得到一个空的关键或价值.weakValuessoftValues

总的来说,我认为基于GC的解决方案不会在这里起作用.您必须具有对连接的强引用才能正确关闭它.(覆盖finalize()可能在这里工作,但这通常是一件破碎的事情.)

相反,我的方法是将引用缓存到某种包装器.就像是

 class ConnectionWrapper {
   private Connection connection;
   private int users = 0;
   private boolean expiredFromCache = false;
   public Connection acquire() { users++; return connection; }
   public void release() {
     users--;
     if (users == 0 && expiredFromCache) {
       // The cache expired this connection.
       // We're the only ones still holding on to it.
     }
   }
   synchronized void tearDown() {
     connection.tearDown();
     connection = null; // disable myself
   }

 }
Run Code Online (Sandbox Code Playgroud)

然后用Cache<Key, ConnectionWrapper>一个RemovalListener看起来像...

 new RemovalListener<Key, ConnectionWrapper>() {
   public void onRemoval(RemovalNotification<Key, ConnectionWrapper> notification) {
     ConnectionWrapper wrapper = notification.getValue();
     if (wrapper.users == 0) {
       // do the teardown ourselves; nobody's using it
       wrapper.tearDown();
     } else {
       // it's still in use; mark it as expired from the cache
       wrapper.expiredFromCache = true;
     }
  }
}
Run Code Online (Sandbox Code Playgroud)

...然后强制用户使用acquire()release()适当的.

我认为,实际上没有比这种方法更好的方法了.检测到没有其他对连接的引用的唯一方法是使用GC和弱引用,但是如果没有强引用它就不能拆除连接 - 这会破坏整个点.您无法保证是RemovalListener连接用户还是连接用户需要拆除连接,因为如果用户需要两分钟以上的时间来做什么呢?我认为这可能是唯一可行的方法.

(警告:上面的代码假定一次只有一个线程会处理;它根本不同步,但希望如果你需要它,那么这足以让你知道它应该如何工作.)