Java Web应用程序:如何实现缓存技术?

时间:2023-01-18 22:25:29

I am developing a Java web application that bases it behavior through large XML configuration files that are loaded from a web service. As these files are not actually required until a particular section of the application is accessed, they are loaded lazily. When one of these files are required, a query is sent to the webservice to retrieve the corresponding file. As some of the configuration files are likely to be used much, much more often than others I'd like to setup some kind of caching (with maybe a 1 hour expiration time) to avoid requesting the same file over and over.

我正在开发一个Java Web应用程序,它通过从Web服务加载的大型XML配置文件来实现它的行为。由于在访问应用程序的特定部分之前实际上不需要这些文件,因此它们会被懒惰地加载。当需要其中一个文件时,会向Web服务发送查询以检索相应的文件。由于某些配置文件可能会被使用得多,比其他配置文件更频繁,我想设置某种缓存(可能有1小时的到期时间)以避免反复请求相同的文件。

The files returned by the web service are the same for all users across all sessions. I do not use JSP, JSF or any other fancy framework, just plain servlets.

对于所有会话中的所有用户,Web服务返回的文件都是相同的。我不使用JSP,JSF或任何其他花哨的框架,只是普通的servlet。

My question is, what is considered a best practice to implement such a global, static cache within a java Web application? Is a singleton class appropriate, or will there be weird behaviors due to the J2EE containers? Should I expose something somewhere through JNDI? What shall I do so that my cache doesn't get screwed in clustered environments (it's OK, but not necessary, to have one cache per clustered server)?

我的问题是,在Java Web应用程序中实现这样一个全局静态缓存的最佳实践是什么?单例类是否合适,或者由于J2EE容器会有奇怪的行为吗?我应该通过JNDI在某处暴露某些东西吗?我该怎么做才能使我的缓存不会在集群环境中搞砸(每个集群服务器有一个缓存可以,但不是必需的)?

Given the informations above, Would it be a correct implementation to put an object responsible for caching as a ServletContext attribute?

鉴于上面的信息,将一个负责缓存的对象作为ServletContext属性是否是一个正确的实现?

Note: I do not want to load all of them at startup and be done with it because that would

注意:我不想在启动时加载所有这些并完成它,因为那会

1). overload the webservice whenever my application starts up
2). The files might change while my application is running, so I would have to requery them anyway
3). I would still need a globally accessible cache, so my question still holds

1)。每当我的应用程序启动时重载webservice 2)。我的应用程序运行时文件可能会更改,所以无论如何我都要重新查询它们3)。我仍然需要一个全局可访问的缓存,所以我的问题仍然存在

Update: Using a caching proxy (such as squid) may be a good idea, but each request to the webservice will send rather large XML query in the post Data, which may be different each time. Only the web application really knows that two different calls to the webservice are actually equivalent.

更新:使用缓存代理(例如squid)可能是个好主意,但是每个对webservice的请求都会在post Data中发送相当大的XML查询,每次都可能不同。只有Web应用程序才真正知道对Web服务的两个不同调用实际上是等效的。

Thanks for your help

谢谢你的帮助

6 个解决方案

#1


Your question contains several separate questions together. Let's start slowly. ServletContext is good place where you can store handle to your cache. But you pay by having cache per server instance. It should be no problem. If you want to register cache in wider range consider registering it into JNDI.

您的问题包含几个单独的问题。让我们慢慢开始。 ServletContext是一个可以存储缓存句柄的好地方。但是你需要为每个服务器实例提供缓存。应该没问题。如果要在更大范围内注册缓存,请考虑将其注册到JNDI中。

The problem with caching. Basically, you are retrieving xml via webservice. If you are accesing this webservice via HTTP you can install simple HTTP proxy server on your side which handle caching of xml. The next step will be caching of resolved xml in some sort of local object cache. This cache can exists per server without any problem. In this second case the EHCache will do perfect job. In this case the chain of processing will be like this Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice).

缓存的问题。基本上,您是通过webservice检索xml。如果您通过HTTP访问此Web服务,您可以在您的一侧安装简单的HTTP代理服务器来处理xml的缓存。下一步将是在某种本地对象缓存中缓存已解析的xml。每个服务器可以存在此缓存而没有任何问题。在第二种情况下,EHCache将做得很好。在这种情况下,处理链将像这样的客户端 - http请求 - > servlet - >查看本地缓存 - 如果没有缓存 - >查看http代理(xml文件) - >执行代理作业(http到webservice)。

Pros:

  • Local cache per server instance, which contains only objects from requested xmls
  • 每个服务器实例的本地缓存,仅包含来自请求的xmls的对象

  • One http proxy running on same hardware as our webapp.
  • 一个http代理在与我们的webapp相同的硬件上运行。

  • Possibility to scale webapp without adding new http proxies for xml files.
  • 可以在不添加xml文件的新http代理的情况下扩展webapp。

Cons:

  • Next level of infrastructure
  • 下一级基础设施

  • +1 point of failure (http proxy)
  • +1点失败(http代理)

  • More complicated deployment
  • 部署更复杂

Update: don't forget to always send HTTP HEAD request into proxy to ensure that cache is up to date.

更新:不要忘记始终将HTTP HEAD请求发送到代理中以确保缓存是最新的。

#2


Here's an example of caching with EhCache. This code is used in several projects to implement ad hoc caching.

以下是使用EhCache进行缓存的示例。此代码在多个项目中用于实现临时缓存。

1) Put your cache in the global context. (Don't forget to add the listener in WEB.XML).

1)将缓存放在全局上下文中。 (不要忘记在WEB.XML中添加监听器)。

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

public class InitializationListener implements ServletContextListener {    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        CacheManager singletonManager = CacheManager.create();
        Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
        singletonManager.addCache(memoryOnlyCache);
        cache = singletonManager.getCache("dbCache");       
        ctx.setAttribute("dbCache", cache );           
    }
}

2) Retrieve the cache instance when you need it. i.e. from a servlet:

2)在需要时检索缓存实例。即来自servlet:

cache = (Cache) this.getContext().getAttribute("dbCache");

cache =(Cache)this.getContext()。getAttribute(“dbCache”);

3) Query the cache just before you do an expensive operation.

3)在执行昂贵的操作之前查询缓存。

        Element e = getCache().get(key);
        if (e != null) {
            result = e.getObjectValue(); // get object from cache
        } else {
            // Write code to create the object you need to cache, then store it in the cache.
            Element resultCacheElement = new Element(key, result);
            cache.put(resultCacheElement);

        }

4) Also don't forget to invalidate cached objects when appropriate.

4)也不要忘记在适当的时候使缓存的对象无效。

You can find more samples here

你可以在这里找到更多样品

#3


Option #1: Use an Open Source Caching Library Such as EHCache

选项#1:使用开源缓存库,如EHCache

Don't implement your own cache when there are a number of good open source alternatives that you can drop in and start using. Implementing your own cache is much more complex than most people realize and if you don't know exactly what you are doing wrt threading you'll easily start reinventing the wheel and resolving some very difficult problems.

当有许多可以插入并开始使用的好的开源替代品时,不要实现自己的缓存。实现自己的缓存要比大多数人意识到的要复杂得多,如果你不确切知道自己在做什么,那么你就可以轻松地重新开发*并解决一些非常棘手的问题。

I'd recommend EHCache it is under an Apache license. You'll want to take a look at the EHCace code samples.

我推荐EHCache它是在Apache许可下。您需要查看EHCace代码示例。

Option #2: Use Squid

选项#2:使用Squid

An even easier solution to your problem would be to use Squid... Put Squid in between the process that requests the data to be cached and the system making the request: http://www.squid-cache.org/

更容易解决问题的方法是使用Squid ...将Squid置于请求缓存数据的进程和发出请求的系统之间:http://www.squid-cache.org/

#4


After doing some more looking around myself, it seems that the easiest way to achieve what I need (within the requirements and acceptable limitations described in the question), would be to add my caching object to the Servlet Context, and looking it up (or passing it around) where needed.

在做了一些自我调查之后,似乎最简单的方法来实现我需要的东西(在问题中描述的要求和可接受的限制内),将我的缓存对象添加到Servlet上下文中,并查找它(或者在需要的地方传递它。

I'd just instantiate my configuration loader from a ServletContextListener, and within the contextInitialized() method, I'd just store it into the ServletContext using ServletContext.setAttribute(). It's then easy to look it up from the servlets themselves using request.getSession().getServletContext().getAttribute().

我只是从ServletContextListener实例化我的配置加载器,并且在contextInitialized()方法中,我只是使用ServletContext.setAttribute()将它存储到ServletContext中。然后使用request.getSession()。getServletContext()。getAttribute()从servlet本身查找它很容易。

I suppose this is the proper way to do it without introducing spring or any other dependency injection framework.

我认为这是在不引入spring或任何其他依赖注入框架的情况下执行此操作的正确方法。

#5


Bref , you can use this ready spring ehcache configuration

Bref,你可以使用这个现成的spring ehcache配置

1- ehcache.xml : show global configuration of Ehcache.

1- ehcache.xml:显示Ehcache的全局配置。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">

    <!-- 
    see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
    Attention: most of those settings will be overwritten by hybris
     -->
    <diskStore path="java.io.tmpdir"/>

</ehcache>

2- ehcache-spring.xml : create EhCacheManagerFactoryBean and EhCacheFactoryBean.

2- ehcache-spring.xml:创建EhCacheManagerFactoryBean和EhCacheFactoryBean。

    <bean id="myCacheManager"  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
        scope="singleton">
        <property name="configLocation" value="ehcache.xml" />
        <property name="shared" value="true" />

    </bean>

 <bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
        <property name="cacheManager" ref="myCacheManager" />
        <property name="cacheName" value="myCache" />
        <property name="maxElementsInMemory" value="1000" />
        <property name="maxElementsOnDisk" value="1000" />
        <property name="eternal" value="false" />
        <property name="diskPersistent" value="true" />
        <property name="timeToIdle" value="600" />
        <property name="timeToLive" value="1200" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
        <property name="statisticsEnabled" value="true" />
        <property name="sampledStatisticsEnabled" value="true" />
    </bean>

3- Inject "myCache" bean in your business class , see the following exemple to get started with getting and putting a object in your cache.

3-在您的业务类中注入“myCache”bean,请参阅以下示例以开始获取并将对象放入缓存中。

@Resource("myCache")
private net.sf.ehcache.Cache myCache; 

@Resource("myService")
private Service myService; 

public byte[] getFromCache(final String code) 
{ 
// init Cache 
final StringBuilder builder = new StringBuilder(); 
 // key to identify a entry in cache map
final String key = code;
// get form the cache 
final Element element = myCache.get(key); 
if (element != null && element.getValue() != null) 
{ 
       return (byte[]) element.getValue(); 
} 

final byte[] somethingToBeCached = myService.getBy(code); 
// store in the cache
myCache.put(new Element(key, somethingToBeCached)); 

return somethingTobeCached; 

} 

#6


I did not had any problems with putting cached object instance inside ServletContext. Do not forget other 2 options (request scope, session scope) with setAttributes methods of this objects. Anything that is supported natively inside webcontainers and j2ee serveers is good (by good I mean it's vendor independed, and without heavy j2ee librarires like Spring). My biggest requirements is that servers gets up and running in 5-10 seconds.

将缓存的对象实例放在ServletContext中没有任何问题。不要忘记使用此对象的setAttributes方法的其他2个选项(请求范围,会话范围)。 webcontainers和j2ee服务器内部支持的任何东西都是好的(好的我的意思是它的供应商独立,没有像Spring这样的重型j2ee库)。我最大的要求是服务器在5-10秒内启动并运行。

I really dislike all caching solution, beacuse it's so easy to get it working on local machine, and hard to get it working on production machines. EHCACHE, Infinispan etc.. Unless you need cluster wide replication / distribution, tightly integrated with Java ecosystem, you can use REDIS (NOSQL datatabase) or nodejs ... Anything with HTTP interface will do. Especially

我真的不喜欢所有的缓存解决方案,因为它很容易让它在本地机器上运行,并且很难让它在生产机器上运行。 EHCACHE,Infinispan等。除非您需要集群范围的复制/分发,与Java生态系统紧密集成,您可以使用REDIS(NOSQL数据库)或nodejs ......任何具有HTTP接口的东西都可以。特别

Caching can be really easy, and here is the pure java solution (no frameworks):

缓存可以非常简单,这里是纯java解决方案(没有框架):

import java.util.*;

/*
  ExpirableObject.

  Abstract superclass for objects which will expire. 
  One interesting design choice is the decision to use
  the expected duration of the object, rather than the 
  absolute time at which it will expire. Doing things this 
  way is slightly easier on the client code this way 
  (often, the client code can simply pass in a predefined 
  constant, as is done here with DEFAULT_LIFETIME). 
*/

public abstract class ExpirableObject {
  public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
  public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;

  protected abstract void expire();

  public ExpirableObject() {
    this(DEFAULT_LIFETIME);
  }

  public ExpirableObject(long timeToLive) {
    Expirer expirer = new Expirer(timeToLive);
    new Thread(expirer).start();
  }

  private class Expirer implements Runnable {  
    private long _timeToSleep;
    public Expirer (long timeToSleep){
      _timeToSleep = timeToSleep;
    }

    public void run() {
      long obituaryTime = System.currentTimeMillis() + _timeToSleep; 
      long timeLeft = _timeToSleep;
      while (timeLeft > 0) {
        try {
          timeLeft = obituaryTime - System.currentTimeMillis();  
          if (timeLeft > 0) {
            Thread.sleep(timeLeft);
          } 
        }
        catch (InterruptedException ignored){}      
      }
      expire();
    }
  }
}

Please refer to this link for further improvements.

请参阅此链接以获得进一步改进。

#1


Your question contains several separate questions together. Let's start slowly. ServletContext is good place where you can store handle to your cache. But you pay by having cache per server instance. It should be no problem. If you want to register cache in wider range consider registering it into JNDI.

您的问题包含几个单独的问题。让我们慢慢开始。 ServletContext是一个可以存储缓存句柄的好地方。但是你需要为每个服务器实例提供缓存。应该没问题。如果要在更大范围内注册缓存,请考虑将其注册到JNDI中。

The problem with caching. Basically, you are retrieving xml via webservice. If you are accesing this webservice via HTTP you can install simple HTTP proxy server on your side which handle caching of xml. The next step will be caching of resolved xml in some sort of local object cache. This cache can exists per server without any problem. In this second case the EHCache will do perfect job. In this case the chain of processing will be like this Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice).

缓存的问题。基本上,您是通过webservice检索xml。如果您通过HTTP访问此Web服务,您可以在您的一侧安装简单的HTTP代理服务器来处理xml的缓存。下一步将是在某种本地对象缓存中缓存已解析的xml。每个服务器可以存在此缓存而没有任何问题。在第二种情况下,EHCache将做得很好。在这种情况下,处理链将像这样的客户端 - http请求 - > servlet - >查看本地缓存 - 如果没有缓存 - >查看http代理(xml文件) - >执行代理作业(http到webservice)。

Pros:

  • Local cache per server instance, which contains only objects from requested xmls
  • 每个服务器实例的本地缓存,仅包含来自请求的xmls的对象

  • One http proxy running on same hardware as our webapp.
  • 一个http代理在与我们的webapp相同的硬件上运行。

  • Possibility to scale webapp without adding new http proxies for xml files.
  • 可以在不添加xml文件的新http代理的情况下扩展webapp。

Cons:

  • Next level of infrastructure
  • 下一级基础设施

  • +1 point of failure (http proxy)
  • +1点失败(http代理)

  • More complicated deployment
  • 部署更复杂

Update: don't forget to always send HTTP HEAD request into proxy to ensure that cache is up to date.

更新:不要忘记始终将HTTP HEAD请求发送到代理中以确保缓存是最新的。

#2


Here's an example of caching with EhCache. This code is used in several projects to implement ad hoc caching.

以下是使用EhCache进行缓存的示例。此代码在多个项目中用于实现临时缓存。

1) Put your cache in the global context. (Don't forget to add the listener in WEB.XML).

1)将缓存放在全局上下文中。 (不要忘记在WEB.XML中添加监听器)。

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

public class InitializationListener implements ServletContextListener {    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        CacheManager singletonManager = CacheManager.create();
        Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
        singletonManager.addCache(memoryOnlyCache);
        cache = singletonManager.getCache("dbCache");       
        ctx.setAttribute("dbCache", cache );           
    }
}

2) Retrieve the cache instance when you need it. i.e. from a servlet:

2)在需要时检索缓存实例。即来自servlet:

cache = (Cache) this.getContext().getAttribute("dbCache");

cache =(Cache)this.getContext()。getAttribute(“dbCache”);

3) Query the cache just before you do an expensive operation.

3)在执行昂贵的操作之前查询缓存。

        Element e = getCache().get(key);
        if (e != null) {
            result = e.getObjectValue(); // get object from cache
        } else {
            // Write code to create the object you need to cache, then store it in the cache.
            Element resultCacheElement = new Element(key, result);
            cache.put(resultCacheElement);

        }

4) Also don't forget to invalidate cached objects when appropriate.

4)也不要忘记在适当的时候使缓存的对象无效。

You can find more samples here

你可以在这里找到更多样品

#3


Option #1: Use an Open Source Caching Library Such as EHCache

选项#1:使用开源缓存库,如EHCache

Don't implement your own cache when there are a number of good open source alternatives that you can drop in and start using. Implementing your own cache is much more complex than most people realize and if you don't know exactly what you are doing wrt threading you'll easily start reinventing the wheel and resolving some very difficult problems.

当有许多可以插入并开始使用的好的开源替代品时,不要实现自己的缓存。实现自己的缓存要比大多数人意识到的要复杂得多,如果你不确切知道自己在做什么,那么你就可以轻松地重新开发*并解决一些非常棘手的问题。

I'd recommend EHCache it is under an Apache license. You'll want to take a look at the EHCace code samples.

我推荐EHCache它是在Apache许可下。您需要查看EHCace代码示例。

Option #2: Use Squid

选项#2:使用Squid

An even easier solution to your problem would be to use Squid... Put Squid in between the process that requests the data to be cached and the system making the request: http://www.squid-cache.org/

更容易解决问题的方法是使用Squid ...将Squid置于请求缓存数据的进程和发出请求的系统之间:http://www.squid-cache.org/

#4


After doing some more looking around myself, it seems that the easiest way to achieve what I need (within the requirements and acceptable limitations described in the question), would be to add my caching object to the Servlet Context, and looking it up (or passing it around) where needed.

在做了一些自我调查之后,似乎最简单的方法来实现我需要的东西(在问题中描述的要求和可接受的限制内),将我的缓存对象添加到Servlet上下文中,并查找它(或者在需要的地方传递它。

I'd just instantiate my configuration loader from a ServletContextListener, and within the contextInitialized() method, I'd just store it into the ServletContext using ServletContext.setAttribute(). It's then easy to look it up from the servlets themselves using request.getSession().getServletContext().getAttribute().

我只是从ServletContextListener实例化我的配置加载器,并且在contextInitialized()方法中,我只是使用ServletContext.setAttribute()将它存储到ServletContext中。然后使用request.getSession()。getServletContext()。getAttribute()从servlet本身查找它很容易。

I suppose this is the proper way to do it without introducing spring or any other dependency injection framework.

我认为这是在不引入spring或任何其他依赖注入框架的情况下执行此操作的正确方法。

#5


Bref , you can use this ready spring ehcache configuration

Bref,你可以使用这个现成的spring ehcache配置

1- ehcache.xml : show global configuration of Ehcache.

1- ehcache.xml:显示Ehcache的全局配置。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">

    <!-- 
    see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
    Attention: most of those settings will be overwritten by hybris
     -->
    <diskStore path="java.io.tmpdir"/>

</ehcache>

2- ehcache-spring.xml : create EhCacheManagerFactoryBean and EhCacheFactoryBean.

2- ehcache-spring.xml:创建EhCacheManagerFactoryBean和EhCacheFactoryBean。

    <bean id="myCacheManager"  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
        scope="singleton">
        <property name="configLocation" value="ehcache.xml" />
        <property name="shared" value="true" />

    </bean>

 <bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
        <property name="cacheManager" ref="myCacheManager" />
        <property name="cacheName" value="myCache" />
        <property name="maxElementsInMemory" value="1000" />
        <property name="maxElementsOnDisk" value="1000" />
        <property name="eternal" value="false" />
        <property name="diskPersistent" value="true" />
        <property name="timeToIdle" value="600" />
        <property name="timeToLive" value="1200" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
        <property name="statisticsEnabled" value="true" />
        <property name="sampledStatisticsEnabled" value="true" />
    </bean>

3- Inject "myCache" bean in your business class , see the following exemple to get started with getting and putting a object in your cache.

3-在您的业务类中注入“myCache”bean,请参阅以下示例以开始获取并将对象放入缓存中。

@Resource("myCache")
private net.sf.ehcache.Cache myCache; 

@Resource("myService")
private Service myService; 

public byte[] getFromCache(final String code) 
{ 
// init Cache 
final StringBuilder builder = new StringBuilder(); 
 // key to identify a entry in cache map
final String key = code;
// get form the cache 
final Element element = myCache.get(key); 
if (element != null && element.getValue() != null) 
{ 
       return (byte[]) element.getValue(); 
} 

final byte[] somethingToBeCached = myService.getBy(code); 
// store in the cache
myCache.put(new Element(key, somethingToBeCached)); 

return somethingTobeCached; 

} 

#6


I did not had any problems with putting cached object instance inside ServletContext. Do not forget other 2 options (request scope, session scope) with setAttributes methods of this objects. Anything that is supported natively inside webcontainers and j2ee serveers is good (by good I mean it's vendor independed, and without heavy j2ee librarires like Spring). My biggest requirements is that servers gets up and running in 5-10 seconds.

将缓存的对象实例放在ServletContext中没有任何问题。不要忘记使用此对象的setAttributes方法的其他2个选项(请求范围,会话范围)。 webcontainers和j2ee服务器内部支持的任何东西都是好的(好的我的意思是它的供应商独立,没有像Spring这样的重型j2ee库)。我最大的要求是服务器在5-10秒内启动并运行。

I really dislike all caching solution, beacuse it's so easy to get it working on local machine, and hard to get it working on production machines. EHCACHE, Infinispan etc.. Unless you need cluster wide replication / distribution, tightly integrated with Java ecosystem, you can use REDIS (NOSQL datatabase) or nodejs ... Anything with HTTP interface will do. Especially

我真的不喜欢所有的缓存解决方案,因为它很容易让它在本地机器上运行,并且很难让它在生产机器上运行。 EHCACHE,Infinispan等。除非您需要集群范围的复制/分发,与Java生态系统紧密集成,您可以使用REDIS(NOSQL数据库)或nodejs ......任何具有HTTP接口的东西都可以。特别

Caching can be really easy, and here is the pure java solution (no frameworks):

缓存可以非常简单,这里是纯java解决方案(没有框架):

import java.util.*;

/*
  ExpirableObject.

  Abstract superclass for objects which will expire. 
  One interesting design choice is the decision to use
  the expected duration of the object, rather than the 
  absolute time at which it will expire. Doing things this 
  way is slightly easier on the client code this way 
  (often, the client code can simply pass in a predefined 
  constant, as is done here with DEFAULT_LIFETIME). 
*/

public abstract class ExpirableObject {
  public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
  public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;

  protected abstract void expire();

  public ExpirableObject() {
    this(DEFAULT_LIFETIME);
  }

  public ExpirableObject(long timeToLive) {
    Expirer expirer = new Expirer(timeToLive);
    new Thread(expirer).start();
  }

  private class Expirer implements Runnable {  
    private long _timeToSleep;
    public Expirer (long timeToSleep){
      _timeToSleep = timeToSleep;
    }

    public void run() {
      long obituaryTime = System.currentTimeMillis() + _timeToSleep; 
      long timeLeft = _timeToSleep;
      while (timeLeft > 0) {
        try {
          timeLeft = obituaryTime - System.currentTimeMillis();  
          if (timeLeft > 0) {
            Thread.sleep(timeLeft);
          } 
        }
        catch (InterruptedException ignored){}      
      }
      expire();
    }
  }
}

Please refer to this link for further improvements.

请参阅此链接以获得进一步改进。