我一直有这样的疑问,比如有一个 Map<String, Student> map ;几个线程同时对这个 map 进行添加,删除,修改里面 Student 里的属性值,是不是这三种操作都需要锁住? 我很明确操作对这个 map 进行添加,删除,获取 size 都是需要锁住的,那么修改里面元素是不是也需要呢? 这个问题主要针对 C++和 Java 。
1
nightwitch 2020-06-19 13:08:16 +08:00
只说 C++
修改元素->加锁,不加的话如果你有两个线程同时修改到了一个 student 那就傻逼了。 增加元素->应该可以不用,map 保证 insert 不会使其他迭代器失效 删除元素->加,虽然 map 保证只影响被删除的元素,但是如果另一个线程在操作被删除的元素就会碰见问题。 |
2
xiangyuecn 2020-06-19 13:13:16 +08:00
既然非线程安全,读写都加锁完事。据我所知:c++不知道,java 、c#的非线程安全 map 内部存在 while(true)类似实现,并发操作存在一定概率会造成死循环,造成程序假死 内存暴涨。
|
3
xuanbg 2020-06-19 13:13:39 +08:00
看你读之后是不是还要写,要写就要锁,只读不需要锁。
|
4
ccpp132 2020-06-19 13:27:57 +08:00
读写都要,所有操作都要。
|
5
shuax 2020-06-19 13:34:04 +08:00
加读写锁
|
6
SmaliYu OP @nightwitch 谢谢您,解决了我多年以来的疑惑,再次谢谢您……
|
7
killmojo 2020-06-19 13:59:46 +08:00
可以参考数据库的四个隔离级别,从需求出发确定什么情况要加锁,比如出现数据只是浏览,出现幻象读无所谓,那就不必读也加锁。
现在很多做电子表格文档协同的也是类似问题,从需求角度出发做好需求的限制,技术上实现就简单一些。 |
8
lxk11153 2020-06-19 14:45:23 +08:00
感觉你没描述清楚,是如下吗?
条件 1. 非线程安全 map 2. 我参考 java hashMap<String, Student>的 3. 锁的颗粒是针对 map 这个实例 1. map.put - 加锁 https://juejin.im/post/5a66a08d5188253dc3321da0 2. map.remove - 不清楚,应该要吧? 3. map.get - 需要吗? 4. map.size - 需要吗? see java.util.HashMap#size() ---- 但你看 java.util.Collections#synchronizedMap(Map<K, V>) 看似把所有方法都加锁了,保证不出错吧或者它这个 synchronized 就是指同步所有 5. student.name="new" - 不需要 ps: 我突然想到 hashCode 的问题, 假设 Student 使用了 https://projectlombok.org/features/EqualsAndHashCode ,map=hashMap<Student, Object> Student s1=new Student("name"); map.put(s1, "name"); s1.name="new"; // 会引起 hashCode 变化吧? map.put(s1, "new"); // map.size 变成 2 了吧,哈哈哈 |
9
BQsummer 2020-06-19 14:51:53 +08:00
生产教训:map 的 put 要加锁,扩容时导致数据丢失( java )
|
11
lniwn 2020-06-19 15:06:02 +08:00
@nightwitch #1 纠正一点,虽然 add 操作不会使迭代器失效,但是会修改节点之间的关系,涉及到红黑树的自平衡,所以任何 update 操作都需要加锁。可以使用读写锁,read 操作加读锁,update 操作加写锁。
|
12
mind3x 2020-06-19 15:15:08 +08:00 via Android
@SmaliYu 他说的是错的,insert 不使其他迭代器失效本来前提就是单线程。多线程读写完全不是一回事
|
13
CRVV 2020-06-19 15:47:11 +08:00
这种事情要看文档的
在 C++ 里,容器上的 const member function 是线程安全的。 然后在这里面 https://en.cppreference.com/w/cpp/container/map const T& at( const Key& key ) const; 是线程安全的 T& operator[]( Key&& key ); 不是线程安全的,所以从 specification 的角度来说,有两个线程都在 m[key] 也是错的。 insert 当然不是线程安全的,明显不可能 必须所有线程上都只使用 const member function 才没有 data race,所以实际情况通常是这些操作全都要加锁。 Java 的 HashMap,https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) 添加删除都需要加锁,修改一个已经存在的 entry 不需要。 |
15
wysnylc 2020-06-19 18:24:49 +08:00
@BQsummer #13 java8 之前的 HashMap 扩容会导致死循环(头插法),java8 之后才是丢数据(尾插法)
解决 map 并发方案简单粗暴,改成 ConcurrentHashMap 或者 ConcurrentSkipListMap(推荐) |
16
Wirbelwind 2020-06-19 18:28:47 +08:00
c u d 都要加锁,不然会发生各种你能想到和想不到的情况
|
17
snnn 2020-06-20 12:19:32 +08:00
No.
|
18
hardwork 2020-08-13 21:04:40 +08:00
c++ std::map 多线程肯定要加锁啊,修改也要加锁啊,增加肯定也要加锁啊.
|