我想定期用java从mysql加载一些系统信息,也许10秒一次,并用一个线程来完成这个,同时,有多个线程需要读取这些信息,有两种情况:< / p>
1,声明public static
地图:
public volatile static Map<String, String> info = new HashMap<String, String>();
// Context.java
// start a thread to load from mysql
private void load() {
new Thread(new Runnable() {
while(true) {
// return a map
info = loadFromDB(); // every time a new map
Thread.sleep(5 * 1000); // sleep 5 seconds
}
}).start();
}
// mutiple threads call this function
public String getConfig(String key) {
return Context.map.get(key);
}
正如代码所示,每次线程从db加载信息时,它都是一个新的地图,我不需要更改map
内容,这个线程是否安全?
2,也许我想要一个有序的地图,并且读取线程需要在地图中获取最大键,在这种情况下,我应该使用TreeMap
还是ConcurrentSkipListMap
?
更新
使用Map
更改volatile
信息以避免使用cpu进行缓存。
答案 0 :(得分:1)
java中的单字段赋值是原子的,所以如果你的线程在loadFromDB()
方法调用中构建一个新的hashmap(所以它没有触及现有的info map),然后只需更改{{1}的值引用它创建的这个新映射的操作是正确的 - 任何其他并发线程都可以看到旧的info值或新的值 - 两者之间没有任何内容。
但有几点:
info
,以避免在更改引用时将值缓存在处理器缓存中并且不更新的情况。这没关系
public static volatile Map...
但这不行
Map infoMap = info;
value1 = infoMap.get("key1");
//do something
value2 = infoMap.get("key2");
因为您可能正在从较新的信息地图中读取value2。
答案 1 :(得分:1)
对于具有共享内存的多线程应用程序,需要记住一条重要规则。关于Java内存模型,JLS7在17.4.5中说明:
乍一看,这句话听起来很怪异。但是,它确切地告诉你你必须知道什么。在你的情况下,它意味着:[..]如果所有顺序一致的执行都没有数据竞争,[..]那么程序的所有执行都将依次为一致的
没有不稳定
如果从变量volatile
的定义中删除限定符info
,则此变量上存在数据竞争,因为两个线程可以访问变量同时,至少一个操作是写操作。这意味着,不能保证顺序一致性,这意味着很难解释。最有可能的是,您会看到各种奇怪的异常,因为读者线程会看到不一致的 HashMap 。
有易失性
如果您将限定符volatile
添加到变量info
的定义中,那么变量info
上也没有数据竞争任何其他内存位置,因为没有内存位置可以同时由两个线程(至少有一个写操作)访问。请注意,对 volatile 变量的操作不是数据竞争。
这意味着在所有执行中,所有操作似乎都按照与程序顺序一致的总顺序执行。这意味着,您的应用程序按预期工作。
答案 2 :(得分:0)
理论上是的,在实践中没有。
理论上,您需要使用并发映射,因为get
的实现可能不是线程安全的。地图实现可以使用不安全的变量进行内部优化,或者对get调用重新哈希,或者执行可能导致某些线程问题的其他事情。
在实践中,据我所知,get调用是一个简单的数组查找,而应是线程安全的。但是,这仅适用于特定实现(Oracle,Windows)上的当前Java版本,并且可能与其他地方不同。
由于在非并发映射上使用并发的成本非常低,我建议使用并发映射,即使只是怀疑它是否是线程安全的。