在我的Java类中,我包含一个Hashmap
变量(类属性)并使用HashMap
运行一些只写put()
的线程:每次写入时它都会存储一个唯一键(由设计完成)。
类方法上的synchronized
关键字是否只适用于安全条件?我的HashMap很简单而不是ConcurrentHashMap
?
答案 0 :(得分:7)
不,仅仅同步写入是不够的。必须对读取和写入内存应用同步。
某些其他线程,某个地方,某个时候,需要读取地图(否则,为什么有地图?),并且需要同步该线程以正确查看地图所代表的内存。它们还需要进行同步,以避免在更新时跳过映射状态中的瞬态不一致。
为了提供一个假设的例子,假设线程1写入了散列映射,其效果仅存储在CPU 1的1级高速缓存中。然后线程2有资格在几秒钟后运行并在CPU 2上恢复;它读取来自CPU 2的1级缓存的hashmap - 它没有看到Thread 1所做的写入,因为在 这两者中写入和读取之间没有内存屏障操作 写作和阅读线程。即使线程1同步写入,然后虽然写入的效果将刷新到主存储器,但线程2仍然看不到它们,因为读取来自1级高速缓存。因此,同步写入仅防止写入上的冲突。
除了CPU缓存之外,JMM还允许线程私有地自己缓存数据,这些数据只需要在内存屏障处刷新到主内存(同步,具有一些特殊限制的volatile,或者完成JMM 5+中不可变对象的构造) )。
要完全理解这个复杂的线程主题, 必须 研究和研究Java内存模型,这对于在线程之间共享数据有什么意义。您必须了解“事先发生”关系和内存可见性的概念,以了解当今多核CPU与各种CPU缓存级别共享数据的复杂性。
如果你不想花时间去理解JMM,那么简单的规则就是两个线程必须在某个地方/以某种方式同步 在同一个对象 之间写入和读取一个线程以查看另一个线程的操作的效果。期。请注意,这并不意味着对象上的所有写入和读取本身都必须同步;在一个线程中创建和配置一个对象然后将其“发布”到其他线程是合法的,只要发布线程和获取线程在同一个对象上同步以进行移交。
答案 1 :(得分:1)
您只需将synchronized
修饰符添加到方法签名中即可。我做了一个简单的例子来向你展示它的实际效果。您可以修改循环以设置任意数量的线程。
它会尝试添加相同的密钥n
次,如果您遇到并发问题,则映射中应该包含重复的密钥。
class MyMap{
private Map<String, Object> map;
public MyMap(){
map = new HashMap<String, Object>();
}
public synchronized void put(String key, Object value){
map.put(key, value);
}
public Map<String, Object> getMap(){
return map;
}
}
class MyRunnable implements Runnable{
private MyMap clazz;
public MyRunnable(MyMap clazz){
this.clazz = clazz;
}
@Override
public void run(){
clazz.put("1", "1");
}
}
public class Test{
public static void main(String[] args) throws Exception{
MyMap c = new MyMap();
for(int i = 0 ; i < 1000 ; i ++){
new Thread(new MyRunnable(c)).start();
}
for(Map.Entry<String, Object> entry : c.getMap().entrySet()){
System.out.println(entry);
}
}
}
答案 2 :(得分:1)
synchronized
写入方法对于线程安全就足够了,只要:
get()
会发生什么。如果你必须在写入哈希映射的同时读取哈希映射,那么最后一点很糟糕;在这种情况下使用ConcurrentHashMap
。
如果你只有一堆并发写入哈希映射,而然后只在一个线程中读取它,你的解决方案应该没问题。