终于研究明白了, concurrenthashmap 的 get 然后 put 的并发问题

2019-06-18 10:48:44 +08:00
 gramyang

我参考的是这篇文章: https://www.cnkirito.moe/java-ConcurrentHashMap-CAS/ 网上论述这个的文章实在是不多。

我的测试代码如下:

public class test2 { private final static Map<String, Table> map = new ConcurrentHashMap<>(); private static final String KEY = "key";

public static void increase1(String key) {
	Table oldTable = map.get(key);
	int value = oldTable.getI();
	oldTable.setI(value + 1);
	map.put(key, oldTable);
}

public static void increase2(String key) {
	Table oldTable;
	Table newTable = new Table(0);
	while(true) {
		oldTable = map.get(KEY);
		newTable.setI(oldTable.getI() + 1);
		if(map.replace(KEY, oldTable, newTable)) break;
	}
}

public static int getTableValue(String key) {
	return map.get(key).getI();
}

public static void main(String[] args) {
	map.put(KEY, new Table(0));
	ExecutorService executor = Executors.newFixedThreadPool(10);
	int callTime = 1000;
	CountDownLatch countDownLatch = new CountDownLatch(callTime);
	for(int i=0; i<callTime; i++) {
		executor.execute(new Runnable() {
			@Override
			public void run() {
				increase2(KEY);
				countDownLatch.countDown();
			}
		});
	}
	try {
		countDownLatch.await();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	executor.shutdown();
	System.out.println("调用次数:" + getTableValue(KEY));
}

}

class Table { private int i;

public Table(int i) {
	this.i = i;
}

public int getI() {
	return i;
}

public void setI(int i) {
	this.i = i;
}

}

测试结果:使用 increase1 的话,调用次数是不停变动的,存在并发错误。而用 increase2 的话,恒定都是 1000.

8037 次点击
所在节点    Java
15 条回复
annoymous
2019-06-18 10:57:52 +08:00
我觉着吧 CAS 的文章到处都是 一搜一大把
WishingFu
2019-06-18 11:07:05 +08:00
我觉得你这个例子跟 Map 关系不大,例 1 是 table 的 get 和 set 同步问题,map 的 put 没有意义,坐等大佬深入科普学习一波
v2lf
2019-06-18 12:26:02 +08:00
CHM 只是保证 table 这个引用对所有的线程可见性(保证对象的正确发布),然而 Table 是不是线程安全的,不是 CHM 能控制的。CHM 每次的 put 也只能确保调用 put 方法的线程,刷新 local 内存到主内存(相当于一个类,只同步了 set 方法,没有同步 get 方法,所以这个类不是线程安全的)。increase2 运行正确 是因为你重新实例化了一个对象,相当于 Table 是事实不可变对象。
v2lf
2019-06-18 12:29:00 +08:00
另外 我个人建议, 网上很多文章都是借鉴来借鉴去··根据我看的经验来说,无法保证正确性,有些文章的描述,是老的 JMM,但是新的 JMM 已经增强了一些同步 y 元语,所以我的建议是看书 以及看 JUC 源码。
[!]( http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#jsr133)
书看 java 并发实战把 大神的那本书
tslling
2019-06-18 12:58:11 +08:00
你变动的是 CHM 里 value 的内部状态怎么能怪 CHM 呢? CHM 只保证这个 value 的同步呀,这里 table 不是线程安全的 CHM 也没办法
alamaya
2019-06-18 13:17:31 +08:00
我觉得这篇文章写得没问题,你的理解可能有点问题
chendy
2019-06-18 13:40:15 +08:00
这里 map 的逻辑完全没用,就是证明了一下 Table 线程不安全…
gramyang
2019-06-18 14:00:50 +08:00
@chendy
@tslling
@v2lf
@WishingFu
不要一看到 map 的 put 行为就两眼放光大呼你肯定是个小白,我当然知道非基础类型的引用是指针,不需要 put 回去。一点笔误而已。

为什么要用 table ?首先你实际代码中用 concurrenthashmap,value 基本上不可能是基本类型或者包装类,都是复合类,用个 table 包装一下再正常不过了。所以网上那种用 AtomicInteger 来实现写安全的基本上没有什么实际意义。

我这个帖子主要验证的是在 while 循环中 replace 成功后 break 的写法,针对的是 put 后 get 的问题,这应该是每个接触 concurrenthashmap 的人都踩过的坑吧??我不觉得这个发现烂大街,一点意义都没有。

replace 方法的问题在于两个参数必须是 value 类型,实际使用中的 value 都是复合类型,不可能是基本类型或者是包装类,所以我加个 table 来验证一下。然而这个时候又涉及到了深拷贝,不过我这里没有体现。
v2lf
2019-06-18 14:15:05 +08:00
@gramyang 大佬 我可没有直呼你是小白,我只是说了我的认为··· 算了, 不讨论了,安心看书···
wysnylc
2019-06-18 14:15:58 +08:00
分布式环境下,公共变量不用 redis 的都是坑嗷
本地(单机)并行处理当我没说
firefffffffffly
2019-06-18 14:21:42 +08:00
@gramyang

每个接触 concurrenthashmap 的人都踩过的坑 ×
不理解线程安全的人踩过的坑 √

increase1 的线程不安全发生在 int value = oldTable.getI(); oldTable.setI(value + 1);这两句上
increase2 使用 CAS 乐观锁的方式解决了 increase1 里线程不安全的问题,你也可以用传统的 synchornized 悲观锁同样解决问题。
无论怎样都没有涉及到 CHM 的任何问题。
micean
2019-06-18 14:23:33 +08:00
多线程操作一个 object,思路不应该是保证这个操作过程是线程安全的么,跟 map 没什么关系啊
像这样
class Table{
void updateSafe();
}

map.get(KEY).updateSafe();
gramyang
2019-06-18 14:38:22 +08:00
@firefffffffffly 是的,我确实对线程安全的理解不够深入
zazalu
2019-06-23 13:16:56 +08:00
希望可以帮忙看下我这个问题,https://www.v2ex.com/t/576609#reply0

从楼主的问题引发出来的后续问题
tslling
2019-09-02 23:30:28 +08:00
@gramyang 我没有两眼放光。我没有大呼你肯定是个小白。不知道你有没有看到,我觉得 12 楼的思路更好,去确保 value 类型的线程安全性。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/574995

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX