java 加载静态缓存的最佳模式或方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1143995/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 15:21:16  来源:igfitidea点击:

what's the best pattern or method to load a static cache?

javastatic

提问by user26270

Let's say I have the following (assume restricted to java 1.4 so no generics) :

假设我有以下内容(假设仅限于 java 1.4,所以没有泛型):

public class CacheManager {
    static HashMap states;
    static boolean statesLoaded;

    public static String getState(String abbrev) {
        if(!statesLoaded) {
            loadStates();
        }
        return (String) states.get(abbrev);
    }

    private static void loadStates() {
        //JDBC stuff to load the data
        statesLoaded = true;
    }
}

In a high-load multi-thread environment like a web app server, this could theoretically have problems if > 1 thread tries to get and load the cache at the same time. (Further assuming there's no startup code on the web app to initialize the cache)

在像 Web 应用程序服务器这样的高负载多线程环境中,如果 > 1 个线程尝试同时获取和加载缓存,则理论上这可能会出现问题。(进一步假设 Web 应用程序上没有用于初始化缓存的启动代码)

Is simply using Collections.synchronizedMap sufficient to fix this? Does the returned synchronizedMap have performance issues when doing get(), if a lot of threads are accessing it?

仅仅使用 Collections.synchronizedMap 就足以解决这个问题吗?如果有很多线程正在访问它,那么在执行 get() 时返回的 synchronizedMap 是否存在性能问题?

Or would it be better to have a non-synchronized HashMap, and instead synchronize on the load method or boolean variable? I would think that if you synchronized either of those, you might end up locking the class.

或者有一个非同步的 HashMap 会更好,而是在加载方法或布尔变量上同步?我认为,如果您同步其中任何一个,您最终可能会锁定课程。

For instance, if the load method was synchronized, what if 2 threads enter the getStates() method at the same time, and both see that statesLoaded is false. The first one gets a lock on the method, loads the cache and sets statesLoaded to true. Unfortunately, the 2nd thread has already evaluated that statesLoaded was false, and proceeds to the load method once the lock is free. Won't it go ahead and load the cache again?

例如,如果 load 方法是同步的,如果 2 个线程同时进入 getStates() 方法,并且都看到 statesLoaded 为 false 会怎样。第一个获取方法的锁,加载缓存并将 statesLoaded 设置为 true。不幸的是,第二个线程已经评估了 statesLoaded 为假,并在锁空闲后继续加载方法。它不会继续并再次加载缓存吗?

回答by sjlee

The best way to load the cache in this case is to take advantage of the JVM static initialization:

在这种情况下加载缓存的最佳方法是利用 JVM 静态初始化:

public class CacheManager {
    private static final HashMap states = new HashMap();

    public static String getState(String abbrev) {
        return (String) states.get(abbrev);
    }

    static {
        //JDBC stuff to load the data
    }
}

The cache will be loaded the first time the class is being used, and since the static initialization is thread safe, the map will be populated safely. Any subsequent calls to retrieve the values can be done without any locking involved.

缓存将在第一次使用类时加载,并且由于静态初始化是线程安全的,映射将被安全地填充。任何检索值的后续调用都可以在不涉及任何锁定的情况下完成。

It is always a good idea to take advantage of static initialization whenever possible. It is safe, efficient, and often quite simple.

尽可能利用静态初始化总是一个好主意。它安全、高效,而且通常非常简单。

回答by Brian Agnew

You should synchronize this check:

您应该同步此检查:

if(!statesLoaded) {
    loadStates();
}

Why ? Multiple threads can get()on the map without any problems. However, you need to atomicallycheck the statesLoadedflag, load the state, and set the flag, check it. Otherwise you could (say) load the states, but the flag would still not be set and be visible as such from another thread.

为什么 ?多个线程可以get()在地图上没有任何问题。但是,您需要原子地检查statesLoaded标志,加载状态,并设置标志,检查它。否则,您可以(例如)加载状态,但该标志仍不会被设置,并且在另一个线程中是可见的。

(You could potentially leave this unsynchronised and allow the possibility of multiple threads to re-initialise the cache, but at the least it's not good programming practise, and at worst could cause you problems further down the line with large caches, differing implementations etc.)

(您可能会保持不同步,并允许多个线程重新初始化缓存,但至少它不是好的编程实践,最坏的情况可能会导致您在大缓存、不同实现等方面进一步出现问题。 )

Consequently, having a synchronised map isn't enough (this is quite a common misunderstanding, btw).

因此,拥有同步地图是不够的(顺便说一句,这是一个很常见的误解)。

I wouldn't worry about the performance impact of synchronisation. It used to be an issue in the past, but is a much more lightweight operation now. As always, measure and optimise when you have to. Premature optimisation is often a wasted effort.

我不会担心同步的性能影响。它过去曾经是一个问题,但现在是一个更轻量级的操作。与往常一样,在必要时进行测量和优化。过早的优化往往是白费力气。

回答by skaffman

Don't try and do this yourself. Use an IoC container like Spring or Guide and get the framework to manage and initialise the singleton for you. This makes your synchronization problems much more manageable.

不要尝试自己做这件事。使用像 Spring 或 Guide 这样的 IoC 容器并获取框架来管理和初始化单例。这使您的同步问题更易于管理。

回答by Trevor Harrison

whats wrong with the Singleton pattern?

单例模式有什么问题?

public class CacheManager {

    private static class SingletonHolder
    {
        static final HashMap states;
        static
        {
            states = new HashMap();
            states.put("x", "y");
        }
    }

    public static String getState(String abbrev) {
        return (String) SingletonHolder.states.get(abbrev);
    }

}

回答by Laserallan

Since statesLoaded only can go from false to true I'd go for a solution where you first check the statesLoaded is true, if it is you just skip the initalization logic. If it isn't you lock and check again and if it's still false you load the states it and set the flag to true.

由于statesLoaded 只能从false 变为true,我会寻找一个解决方案,您首先检查statesLoaded 是否为true,如果是,则只需跳过初始化逻辑。如果不是,则锁定并再次检查,如果仍然为假,则加载状态并将标志设置为真。

This means that any thread calling getState after the cache is initialized will "early out" and use the map without locking.

这意味着任何在缓存初始化后调用 getState 的线程都将“提前退出”并在不锁定的情况下使用映射。

something like:

就像是:

// If we safely know the states are loaded, don't even try to lock
if(!statesLoaded) {
  // I don't even pretend I know javas synchronized syntax :)
  lock(mutex); 
  // This second check makes sure we don't initialize the
  // cache multiple times since it might have changed
  // while we were waiting for the mutex
  if(!statesLoaded) {
    initializeStates();
    statesLoaded = true;
  }
  release(mutex);
}
// Now you should know that the states are loaded and they were only
// loaded once.

This means that the locking will only be involved before and during the actual initalization happens.

这意味着锁定只会在实际初始化发生之前和期间涉及。

If this would be C, I'd also make sure to make the statesLoadedvariableto be volatile to make sure the compiler optimize the second check. I don't know how java behaves when it comes to situations like that but I would guess it considers all shared data such as statesLoaded to be potentially dirty when going into synchronization scopes.

如果这将是 C,我还会确保使statesLoadedvariablevolatile 以确保编译器优化第二次检查。我不知道 java 在这种情况下的行为方式,但我猜它认为所有共享数据(如 statesLoaded)在进入同步范围时可能是脏的。

回答by alexey28

+1 for IoC container. Use Spring. Create CacheManager class as non static one and define CacheManaget in Spring context config.

+1 对于 IoC 容器。使用弹簧。将 CacheManager 类创建为非静态类,并在 Spring 上下文配置中定义 CacheManaget。

1 Non-static CacheManager version

1 非静态CacheManager版本

package your.package.CacheManager;

// If you like annotation
@Component
public class CacheManager<K, V> {

    private Map<K, V> cache;

    public V get(K key) {
        if(cache != null) {
            return cache.get(key);
        }
        synchronized(cache) {
            if(cache == null) {
                loadCache();
            }
            return cache.get(key);
        }
    }

    private void loadCache() {
        cache = new HashMap<K, V>();
        // Load from JDBC or what ever you want to load
    }
}

2 Define bean of CacheManager in spring context or use @Service/@Component annotation (don't foget define scan path for annotations)

2 在spring上下文中定义CacheManager的bean或者使用@Service/@Component注解(不要忘记为注解定义扫描路径)

<bean id="cacheManager" class="your.package.CacheManager"/>

3 Inject your cache bean what ever you want with Spring config or @Autowire annotation

3 使用 Spring 配置或 @Autowire 注释注入您想要的缓存 bean

<bean id="cacheClient" clas="...">
    <property name="cache" ref="cacheManager"/>
</bean>