为了提高速度,我有一个Hashmap,希望不需要加锁。假设我不介意过期数据,同时更新和访问它是否会引起任何问题?
我的访问是获取操作,而不是遍历,删除操作也是更新的一部分。
为了提高速度,我有一个Hashmap,希望不需要加锁。假设我不介意过期数据,同时更新和访问它是否会引起任何问题?
我的访问是获取操作,而不是遍历,删除操作也是更新的一部分。
是的,这会造成重大问题。一个例子就是在将值添加到哈希映射时可能会发生什么:这可能会导致表重新哈希化,如果在另一个线程正在遍历碰撞列表(哈希表“桶”)时发生了这种情况,那个线程可能会错误地找不到在映射中存在的键。
HashMap
明确不适合并发使用。
应该使用 ConcurrentHashMap
代替。
同步或使用ConcurrentHashMap的重要性不容小觑。
直到几年前,我错误地认为只同步HashMap上的put和remove操作就足够了。这当然是非常危险的,在某些jdk版本(早期的1.5版本)中实际上会导致HashMap.get()进入无限循环。
我在几年前所做的事情(而真正不应该做的):
public MyCache {
private Map<String,Object> map = new HashMap<String,Object>();
public synchronzied put(String key, Object value){
map.put(key,value);
}
public Object get(String key){
// can cause in an infinite loop in some JDKs!!
return map.get(key);
}
}
编辑:我想加一个例子,说明不要做什么(请参见上文)
当你有疑问时,要查看类的Javadocs:
请注意,此实现未同步。如果多个线程同时访问哈希映射,并且其中至少一个线程在结构上修改了映射,则必须通过外部同步来进行同步。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已经包含的键相关联的值不是结构修改。)这通常是通过在自然封装映射的某个对象上同步完成的。如果没有这样的对象,则应使用Collections.synchronizedMap方法“包装”该映射。最好在创建时进行此操作,以防止意外的非同步访问映射:
Map m = Collections.synchronizedMap(new HashMap(...));
(强调不是我)
因此,根据您说的线程将从Map中删除映射的事实,答案是是的它肯定会引起问题并且是绝对不安全的。
是的。会发生非常糟糕的事情。例如,您的线程可能会陷入无限循环。
要么使用ConcurrentHashMap,要么使用NonBlockingHashMap。
ConcurrentHashMap
即可。 - Johnny你描述的条件无法满足HashMap
。由于更新映射的过程不是原子性的,因此可能会遇到无效状态的映射。多次写入可能会导致其处于损坏状态。ConcurrentHashMap(1.5或更高版本)可以实现你想要的功能。
我在这里或其他地方读到,不,你不能从多线程访问,但没有人说发生了什么。
所以,今天我看到了(这就是我在这个老问题上的原因),一个自三月份以来一直在生产中运行的应用程序:将2个放在相同的HashSet(然后是HashMap)上会导致CPU超载(接近100%),内存增加3GB,然后因为垃圾回收而下降。 我们不得不重新启动应用程序。
不会有任何问题,只要您按照以下步骤操作:
在任何多线程发生之前,在单个线程的第一次加载时将数据放入HashMap中。这是因为添加数据的过程会改变modcount,并且在第一次添加数据时(将返回null)与替换数据时(旧数据将被返回,但modcount不会被改变)是不同的。Modcount是使迭代器快速失败的原因。但如果您使用get,则不会进行任何迭代,所以没问题。
在整个应用程序中使用相同的键。一旦应用程序启动并加载其数据,就不能将其他键分配给此映射。这样,get将获取过期数据或插入新数据的数据 - 不会出现任何问题。
像其他人提到的那样,使用ConcurrentHashMap或在更新时同步地图。