【问题标题】:Thread Safe implementation for In-Memory cache内存中缓存的线程安全实现
【发布时间】:2015-05-24 04:00:55
【问题描述】:
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.naming.NamingException;

import org.joda.time.DateTime;
import org.kp.oppr.esb.logger.Logger;
import org.springframework.beans.factory.annotation.Autowired;

public class CachedCrlRepository {

    private static final Logger LOGGER = new Logger("CachedCrlRepository");

    private final Map<URI, SoftReference<X509CRL>> crlCache = Collections
            .synchronizedMap(new HashMap<URI, SoftReference<X509CRL>>());;

    private static int DEFAULT_CACHE_AGING_HOURS;

    @Autowired
    private DgtlSgntrValidator validator;

    @Autowired
    private  CrlRepository crlRepository;

    public X509CRL findCrl(URI crlUri, X509Certificate issuerCertificate,
            Date validationDate) throws DigitalValdiationException,
            CertificateException, CRLException, IOException, NamingException {
        SoftReference<X509CRL> crlRef = this.crlCache.get(crlUri);
        if (null == crlRef) {
            LOGGER.info("Key CRL URI : " + crlUri +  "  not found in the cache " );
            return refreshCrl(crlUri, issuerCertificate, validationDate);
        }
        X509CRL crl = crlRef.get();
        if (null == crl) {
            LOGGER.info("CRL Entry garbage collected: " + crlUri);
            return refreshCrl(crlUri, issuerCertificate, validationDate);
        }
        if (validationDate.after(crl.getNextUpdate())) {
            LOGGER.info("CRL URI  no longer valid: " + crlUri);
            LOGGER.info("CRL validation date: " + validationDate + " is after CRL next update date: " + crl.getNextUpdate());
            return refreshCrl(crlUri, issuerCertificate, validationDate);
        }

        Date thisUpdate = crl.getThisUpdate();
        LOGGER.info("This update " + thisUpdate);

        /*
         * The PKI the nextUpdate CRL extension indicates 7 days. The
         * actual CRL refresh rate is every 3 hours. So it's a bit dangerous to
         * only base the CRL cache refresh strategy on the nextUpdate field as
         * indicated by the CRL.
         */

        DateTime cacheMaturityDateTime = new DateTime(thisUpdate)
                .plusHours(DEFAULT_CACHE_AGING_HOURS);
        LOGGER.info("Cache maturity Date Time " + cacheMaturityDateTime);
        if (validationDate.after(cacheMaturityDateTime.toDate())) {
            LOGGER.info("Validation date: "  + validationDate + " is after cache maturity date: " + cacheMaturityDateTime.toDate());
            return refreshCrl(crlUri, issuerCertificate, validationDate);
        }
        LOGGER.info("using cached CRL: " + crlUri);
        return crl;
    }

    public static int getDEFAULT_CACHE_AGING_HOURS() {
        return DEFAULT_CACHE_AGING_HOURS;
    }

    public static void setDEFAULT_CACHE_AGING_HOURS(int dEFAULT_CACHE_AGING_HOURS) {
        DEFAULT_CACHE_AGING_HOURS = dEFAULT_CACHE_AGING_HOURS;
    }

    private X509CRL refreshCrl(URI crlUri, X509Certificate issuerCertificate,
            Date validationDate) throws DigitalValdiationException,
            CertificateException, CRLException, IOException, NamingException {
        X509CRL crl = crlRepository.downloadCRL(crlUri.toString());
        this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
        return crl;
    }




}

我有这个类 CachedCrlrepository 存储来自特定提供者的 CRL 列表。我想知道我的实现是否是线程安全的,或者我在这里遗漏了一些东西。缓存用于 Web 服务,因此它是多线程的。

我对这种特殊方法的怀疑

private X509CRL refreshCrl(URI crlUri, X509Certificate issuerCertificate,
                Date validationDate) throws DigitalValdiationException,
                CertificateException, CRLException, IOException, NamingException {
            X509CRL crl = crlRepository.downloadCRL(crlUri.toString());
            this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
            return crl;
        }

我认为这个特定的行需要同步

this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));

 synchronized(this)
{
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));

}

我看到的另一个问题是,在运行 GC 之后,缓存中仍然有该条目在内存中。它从不执行这行代码

if (null == crl) {
            LOGGER.info("CRL Entry garbage collected: " + crlUri);
            return refreshCrl(crlUri, issuerCertificate, validationDate);
        } 

【问题讨论】:

  • 我真的很困惑,你是否希望它是线程安全的
  • 考虑改用Caffeine 之类的库。
  • 回复。 put():不,对Collections.synchronizedMap() 的调用会为您完成。不过我不喜欢使用SoftReference,这不是一个很好的做法。
  • 我不相信能以权威正确的方式回答这个问题。 “这个线程是否安全”这个问题的答案必然取决于为一个或多个类建立的线程安全策略,以及这些类的实例的使用是否违反了该策略。由于您不阐明政策,因此无法知道。归根结底,线程安全只是关于保护可变状态......并且有多种方法可以实现这一目标(以及多种方法来犯错误)。
  • @Saurabh Jhunjhunwala -- 希望它是线程安全的

标签: java multithreading spring


【解决方案1】:

一般情况下,如果您期望对象(在这种情况下为crlCache)出现大量流量和高并发访问,则不应使用同步映射。对于每一个读或写线程都会在另一个之后等待并且负载很重,你的线程数会很高,最终你的服务器会崩溃。您可以查看ConcurrentHashMap。它旨在在这种情况下高效工作。

你的第二点:

synchronized(this)
{
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));

}

当前代码完全不需要,因为 put 方法已经同步。

为了最小的变化替换

private final Map<URI, SoftReference<X509CRL>> crlCache = Collections
            .synchronizedMap(new HashMap<URI, SoftReference<X509CRL>>());;

private final ConcurrentHashMap<URI, SoftReference<X509CRL>> crlCache = new ConcurrentHashMap<URI, SoftReference<X509CRL>>();

最后,使用SoftReference 很好,但还有更好的选择。来自 google 的Guava 是一个非常强大且高效的缓存构建器。

【讨论】:

  • 嗯,不是“必须”,但我同意ConcurrentHashMap 可能是一个更好的选择,如果有任何高需求/线程的机会。
  • “首先不要将同步 Map 用于多线程环境,因为它们会导致严重的性能问题。”当然,这是个坏建议。对于争用较低的任何情况,同步收集都是完全可以接受的解决方案。对象状态访问的序列化预计只会在并发访问高从而产生高争用的情况下损害性能。此外,在并发访问不可用的情况下对原子性的要求使同步成为一种简单且通常是干净的首过解决方案。
  • @scottb:感谢您的建议,并进行了相应的编辑以发表更温和的声明。
猜你喜欢
  • 1970-01-01
  • 2015-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 1970-01-01
  • 1970-01-01
  • 2012-07-11
相关资源
最近更新 更多