class Q2 {
static long value;
public static void main(String[] args) throws InterruptedException{
Runnable r = () -> {
for (int i = 0; i < 100000000; i++) {
synchronized (Q2.class) {
value++;
}
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
Thread.sleep(100000); //wait enough time
System.out.println("value = " + value);
}
}
我知道如果没有 synchronized 关键字,由于并发问题,输出不会是 200000000。
但是,由于没有附加volatile关键字,我认为可见性问题应该出现在每个线程读取的value值中。
我多次尝试该代码,但输出始终是 200000000。
这只是巧合还是运气?
但是,由于没有附加volatile关键字,我认为可见性问题应该出现在每个线程读取的value值中。
A
synchronized
块负责内存同步和互斥锁定。它在开始时有一个读取内存屏障,在结束时有一个写入内存屏障,这意味着每个线程在递增它时都会看到最新版本的value
,并且它们将发布更新后的值离开synchronized
块。两个线程都在 Q2.class
实例上同步,因此一次只有一个线程执行 value++
。
关于代码的一些一般性评论:
t1.join(); t2.join();
而不是睡觉。 join()
等待线程结束,并确保主线程获得被加入的线程所做的任何更新。这也意味着主线程将在后台线程完成时立即继续,而无需休眠任意时间。AtomicInteger
来包装一个 volatile int
字段,并允许您在没有 synchronized
块的情况下进行增量。在Java中,任何同步动作都保证了可见性。规范写道:
同步动作引发动作的同步关系,定义如下:
监视器 m 上的解锁操作与 m 上的所有后续锁定操作同步(其中“后续”是根据同步顺序定义的)。
对易失性变量 v 的写入(§8.3.1.4)与任何线程对 v 的所有后续读取同步(其中“后续”是根据同步顺序定义的)。
启动线程的动作与它启动的线程中的第一个动作同步。
将默认值(零、假或空)写入每个变量与每个线程中的第一个操作同步。
线程 T1 中的最终操作与另一个线程 T2 中检测到 T1 已终止的任何操作同步。
T2 可以通过调用 T1.isAlive() 或 T1.join() 来实现。
如果线程 T1 中断线程 T2,则 T1 的中断与任何其他线程(包括 T2)确定 T2 已被中断(通过抛出 InterruptedException 或通过调用 Thread.interrupted 或 Thread.isInterrupted)的任何点同步。
继续说(强调我的):
两个动作可以通过happens-before关系来排序。如果一个动作发生在另一个动作之前,那么第一个 对 是可见的并且在第二个之前排序。 如果我们有两个动作 x 和 y,我们写 hb(x, y) 来表示 x happens-before y。
- 如果 x 和 y 是同一个线程的动作,并且 x 在程序顺序中出现在 y 之前,则 hb(x, y)。
- 从对象构造函数的末尾到该对象的终结器(§12.6)的开始有一个发生前的边缘。
- 如果动作 x synchronizes-with 后续动作 y,那么我们也有 hb(x, y)。
- 如果 hb(x, y) 和 hb(y, z),则 hb(x, z)。