通过方法公开的Servlet静态字段--需要数据完整性

3

我写了一个名为AbcServlet.java的Servlet,在其中有一个名为cacheSeller的静态字符串字段。该字段在servlet的initdoGet方法中被填充,同时它也可以通过如下所示的CacheSellerClearThread.java线程进行清空:

AbcServlet.java

public class AbcServlet extends HttpServlet {

    private static String cacheSeller = null;

    @Override
    public void init() throws ServletException {
        super.init();
        cacheSeller = populateCacheSeller();
    }

    /**
     * As soon as multiple requests are coming doGet is being called
     */
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        synchronized (this.getClass()) {
            if (cacheSeller == null) {
                cacheSeller = populateCacheSeller();
            }
        }

    }

    private String populateCacheSeller() {
        String fetchItFromSomewhere = "";// some logic to fetch the sting
        return fetchItFromSomewhere;
    }

    public static synchronized void clearCacheSeller() {
        cacheSeller = null;
    }
}

CacheSellerClearThread.java

/**
 * This thread is clearing the string field cacheSeller of AbcServlet 
 *
 */
public class CacheSellerClearThread extends Thread {

    public void run() {
        while (true) {
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Here it is clearing the static string field cacheSeller of AbcServlet
            AbcServlet.clearCacheSeller();
        }
    }
}

如果您查看代码,可以发现cacheSeller的值在doGet方法中被读取和修改。所有请求线程都会执行doGet,在此同时CacheSellerClearThread将在每2秒钟清除其值。因此,为了维护cacheSeller值的数据完整性,我使用同步。

我需要一个建议,是否有其他方法可以在clearCacheSeller方法中不使用显式同步,也不在Servlet中应用显式类级锁定的情况下实现这一点(我是指使用任何高级并发API,如原子引用或您可以建议的其他API)。

我已经使用AtomicReference编写了新的实现,请建议,我是否需要在任何地方使用同步。

public class AbcServlet extends HttpServlet {

    private static AtomicReference<String> cacheSeller = new AtomicReference<String>();

    @Override
    public void init() throws ServletException {
        super.init();
        cacheSeller.set(populateCacheSeller());
    }

    /**
     * As soon as multiple requests are coming doGet is being called
     */
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        if (cacheSeller.get() == null) {
            cacheSeller.compareAndSet(null, populateCacheSeller());
        }
        resp.getWriter().print(cacheSeller.get());
    }

    private String populateCacheSeller() {
        String fetchItFromSomewhere = "";// some logic to fetch the sting
        return fetchItFromSomewhere;
    }

    public static void clearCacheSeller() {
        cacheSeller.set(null);
    }
}

1
首先,在servlet类中拥有静态实例变量从来都不是一个好主意。 - niiraj874u
@niiraj874u 我同意你的观点,但这是根据设计无法更改的。 - Sunny Gupta
是的,我知道init方法只会被调用一次,但cacheSeller的值也在doGet方法中被读取和修改。所有请求线程都将执行doGet,同时CacheSellerClearThread将在每2秒后清除其值。 - Sunny Gupta
@niiraj874u 我已经修改了问题的最后一行,我认为它会变得更清晰。 - Sunny Gupta
为什么您在init方法和doGet方法中都调用populateCacheSeller方法两次? - Dimitri
显示剩余2条评论
2个回答

2

我建议使用Executors框架。

Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                AbcServlet.clearCacheSeller();
            }
        }, 2L,2L,TimeUnit.SECONDS);

在你的doGet中,只需要同步初始化:

 @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String localCacheSeller = cacheSeller; 
            if (cacheSeller == null) {
                synchronized (this.getClass()) {
                     if (cacheSeller == null) { //need this because cacheSeller may already have been initialized while we waited
                         cacheSeller = populateCacheSeller();
                         localCacheSeller = cacheSeller; //because the executor may clear it after we initialized
                     }
                }
            }
    }

基本上,您建议我使用Executor Framework创建调度程序的方法。但是我需要一些避免显式锁定的方法。顺便说一句,感谢您建议使用双重检查来检查null值。 - Sunny Gupta

0
你可以使用类似 Google Guava 这样的工具来创建缓存。
例如:
LoadingCache<String, String> cache =
 CacheBuilder.newBuilder()
 .expireAfterWrite(2, TimeUnit.MINUTES)
 .build(loader);

有了这样的东西,您可以避免次要线程清除缓存。

这个实现是一个简单的内存缓存,也是线程安全的。

敬礼


使用Java并发API类,除了同步,是否有可能使用其他东西?我的意思是任何抽象锁定功能的API。感谢使用Guava API的解决方案。 - Sunny Gupta
1
我建议使用AtomicReference。 - Dimitri
AtomicReference是一种封装引用(或对象)的类,并保证在该对象内所有操作(读/更新)都是原子性的。 - Dimitri
是的,Pedro,你说得对。现在我不想去考虑这个问题:)。你的解决方案更好。@SAM,没有必要检查变量是否为null。构造函数AtomicReference()已经将当前引用设置为null。每次需要更新值时,只需调用compareAndSet方法即可。 - Dimitri
@Dimitri 我进行了空值检查,因为我不想每次都调用populateCacheSeller方法。如果值为空,则只调用compareAndSet并调用populateCacheSeller。我的意思清楚吗? - Sunny Gupta
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接