假设我有以下内容(假设仅限于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;
}
}
在像Web应用服务器这样的高负载多线程环境中,理论上如果> 1个线程尝试同时获取并加载缓存。 (进一步假设Web应用程序上没有用于初始化缓存的启动代码)
只是使用Collections.synchronizedMap足以解决这个问题吗?如果很多线程正在访问它,那么返回的synchronizedMap在执行get()时会出现性能问题吗?
或者更好的是有一个非同步的HashMap,而是在load方法或boolean变量上同步?我认为如果你同步其中任何一个,你可能会最终锁定该类。
例如,如果load方法已同步,那么如果两个线程同时进入getStates()方法,并且两者都看到statesLoaded为false。第一个获取方法的锁定,加载缓存并将statesLoaded设置为true。不幸的是,第二个线程已经评估了statesLoaded为false,并且一旦锁定空闲就进入load方法。它不会继续并再次加载缓存吗?
答案 0 :(得分:6)
在这种情况下加载缓存的最佳方法是利用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
}
}
缓存将在第一次使用类时加载,并且由于静态初始化是线程安全的,因此将安全地填充映射。任何后续的检索值都可以在不涉及任何锁定的情况下完成。
尽可能利用静态初始化始终是个好主意。它安全,高效,而且通常很简单。
答案 1 :(得分:1)
您应该同步此检查:
if(!statesLoaded) {
loadStates();
}
为什么?地图上的多个线程get()
可以没有任何问题。但是,您需要原子检查statesLoaded
标志,加载状态并设置标志,检查它。否则你可以(比方说)加载状态,但是标志仍然没有被设置并且从另一个线程可见。
(你可能会让这个不同步并允许多个线程重新初始化缓存的可能性,但至少它不是很好的编程习惯,并且最坏的情况可能会导致你的问题进一步发生大缓存,不同实现等。)
因此,拥有同步地图是不够的(这是一个很常见的误解,顺便说一下。)
我不担心同步对性能的影响。它曾经是一个过去的问题,但现在是一个更轻量级的操作。一如既往,在必要时进行测量和优化。过早优化往往是一种浪费的努力。
答案 2 :(得分:0)
请勿尝试自己动手。使用像Spring或Guide这样的IoC容器,获取框架来管理和初始化单例。这使您的同步问题更易于管理。
答案 3 :(得分:0)
Singleton模式有什么问题?
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);
}
}
答案 4 :(得分:0)
由于statesLoaded只能从false变为true,所以我会寻求一个解决方案,首先检查statesLoaded是否为真,如果只是跳过初始化逻辑。如果不是你锁定并再次检查,如果它仍然是假的,你加载状态并将标志设置为真。
这意味着在初始化缓存之后调用getState的任何线程都将“提前”并使用映射而不会锁定。
类似的东西:
// 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.
这意味着锁定只会在实际初始化之前和期间发生。
如果这是C,我还要确保statesLoaded
variable
是易变的,以确保编译器优化第二次检查。我不知道java在这种情况下的表现如何,但我猜它会认为所有共享数据(例如statesLoaded)在进入同步范围时可能会变脏。
答案 5 :(得分:0)
+1。使用Spring。将CacheManager类创建为非静态类,并在Spring上下文配置中定义CacheManaget。
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在spring上下文中定义CacheManager的bean或使用@Service / @Component注释(不要为注释定义扫描路径)
<bean id="cacheManager" class="your.package.CacheManager"/>
3使用Spring配置或@Autowire注释
注入您想要的缓存bean<bean id="cacheClient" clas="...">
<property name="cache" ref="cacheManager"/>
</bean>