求教,为什么这么做是线程不安全的?

2019-03-06 14:12:32 +08:00
 hjchjc1993
public class DeadLock {
	private Integer a = 0;
	private Integer b = 0;

	private void createDeadLock() {
		Runnable first = () -> {
			for (int i = 0; i < 100000; i++) {
				synchronized(this.a) {
					System.out.println("锁住 a");
					this.a++;
					synchronized(this.b) {
						this.b++;
					}
					System.out.println("对 a、b 的处理完成:" + a + " " + b);
				}
			}
		};

		Runnable second = () -> {
			for (int i = 0; i < 100000; i++) {
				synchronized(this.b) {
					System.out.println("锁住 b");
					this.b++;
					synchronized(this.a) {
						this.a++;
					}
					System.out.println("对 a、b 的处理完成:" + a + " " + b);
				}
			}
		};

		Thread firstThread = new Thread(first);
		Thread secondThread = new Thread(second);
		firstThread.start();
		secondThread.start();
	}

	public static void main(String[] args) {
		DeadLock deadLock = new DeadLock();
		deadLock.createDeadLock();
	}
}
2715 次点击
所在节点    问与答
19 条回复
hjchjc1993
2019-03-06 14:27:24 +08:00
本来想搞一个死锁出来,运行了多次,结果是死锁没有出现,反而 a 和 b 的值表示这么做是线程不安全的,求教啊,别沉
hjchjc1993
2019-03-06 14:29:33 +08:00
再顶下~
gaius
2019-03-06 14:41:42 +08:00
输出语句没同步
geelaw
2019-03-06 14:48:04 +08:00
Integer 对象是不可变对象,假设 x 是一个 Integer,则 x++ 等同于 x = new Integer(x.intValue() + 1),但是你锁住的对象是之前的对象,是 x 引用了谁改变了,而不是 x 引用的那个谁改变了。
gtexpanse
2019-03-06 14:53:42 +08:00
++不是原子操作外加对象引用其实变了,所以没有锁住吧?
ipwx
2019-03-06 14:55:06 +08:00
你把两条 System.out.println 语句输出的内容做一下细微变化再看看。
hjchjc1993
2019-03-06 15:32:02 +08:00
@geelaw synchronized 貌似只能锁对象,那这种情况该怎么修改代码才能线程安全呢?
gaius
2019-03-06 15:37:02 +08:00
首先 Integer a =0; Integer b=0;是同一个对象,一把锁。synchronized 里的对象改变之后,原对象的锁就释放了。
其实你锁的是数字。
hjchjc1993
2019-03-06 15:54:54 +08:00
@gaius 确实应该是没锁住,对 JVM 的内存模型还是不太清楚啊。
ihavecat
2019-03-06 16:00:06 +08:00
Integer -128 到 127 是放在缓存里的,你这种写法和 a 和 b 是指向同一地址的
hjchjc1993
2019-03-06 16:13:50 +08:00
@ihavecat 改为了
private Integer a = new Integer(200);
private Integer b = new Integer(200);
最后的结果显示仍然不是线程安全的
Malthael
2019-03-06 16:35:11 +08:00
把 b 改个对象,比如说 Double,锁对象时用 synchronized (Integer.class)和 synchronized (Double.class)
Yuicon
2019-03-06 16:39:01 +08:00
@hjchjc1993 写个包装类
hjchjc1993
2019-03-06 16:47:03 +08:00
@Malthael 其实用其它的写法是很容易做到线程安全的,我只是对这种写法上锁失败的原因表示好奇。。
ihavecat
2019-03-06 16:48:07 +08:00
synchronized 中锁住的对象不能被改变,在循环体内进行++操作后,对象变了,各自没锁住,就没法死锁了,建议用字符串或者 AtomicInteger 试试
@hjchjc1993
brainfxxk
2019-03-06 16:58:13 +08:00
Integer 类的问题 你把锁换成 private final Object 累加的数值分别加到一个 int/Integer 字段上 再试试
hjchjc1993
2019-03-06 17:11:41 +08:00
这样写就没问题了。原来的问题可能确实出现在没有锁 final 对象,自增改变了对象,所以没锁住。
下面这么样写就死锁了。。。

public class DeadLockProblem {

private final Counter counter1 = new Counter();
private final Counter counter2 = new Counter();

private void createDeadLock() {
Runnable first = () -> {
for (int i = 0; i < 100000; i++) {
synchronized(counter1) {
System.out.println("锁住 counter1");
counter1.addOne();
synchronized(counter2) {
counter2.addOne();
}
System.out.println("对 counter1、counter2 的处理完成:" + counter1.getCount() + " " + counter2.getCount());
}
}
};

Runnable second = () -> {
for (int i = 0; i < 100000; i++) {
synchronized(counter2) {
System.out.println("锁住 counter2");
counter2.addOne();
synchronized(counter1) {
counter1.addOne();
}
System.out.println("对 counter1、counter2 的处理完成:" + counter1.getCount() + " " + counter2.getCount());
}
}
};

Thread firstThread = new Thread(first);
Thread secondThread = new Thread(second);
firstThread.start();
secondThread.start();
}

public static void main(String[] args) {
DeadLockProblem deadLock = new DeadLockProblem();
deadLock.createDeadLock();
}
}

class Counter {
private int count = 0;

void addOne() {
count++;
}

int getCount() {
return count;
}
}
hjchjc1993
2019-03-06 17:56:09 +08:00
@brainfxxk 谢谢 正解
MachineSpirit
2019-03-06 18:40:51 +08:00
我一直以为 synchronized 锁的是对象的引用。想了想只锁引用也确实不安全。应该是对象和引用都被锁了吧?

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

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

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

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

© 2021 V2EX