我正在查看我发现的最简单的示例之一,并开始推理 SO
(同步顺序)或更准确地说,缺少它。考虑以下示例:
int a, b; // two shared variables
Thread-X:
void threadX() {
synchronized(this) {
a = 1;
}
synchronized(this) {
b = 1;
}
}
还有一个读者线程,Thread-Y
:
void threadY() {
int r1 = b;
int r2 = a;
}
为简单起见,我们假设 Thread-Y
完全按照以下顺序执行读取:它肯定会首先读取 b
,然后是 a
(与写作相反)。
允许读取线程查看[1, 0]
(比如b=1
发生在之前 a=1
)。我想我也明白为什么:因为有 no synchronization order在这两个 Action 之间,因此没有happens-before,根据JLS
,这是一个数据竞赛:
When a program contains two conflicting accesses that are not ordered by a happens-before relationship, it is said to contain a data race.
因此读取 a
和 b
是两个有趣的读取,所以看到 b=1
和 a=0
是允许和可能的。
现在这又允许 JVM 在 writer 中进行锁粗化,所以它变成:
void threadX() {
synchronized(this) {
a = 1;
b = 1;
}
}
我的问题是,如果读者最初是这样写的:
void threadY() {
synchronized(this) {
int r1 = b;
}
synchronized(this) {
int r2 = a;
}
}
还会允许锁粗化吗?我认为我知道答案,但我也想听到有根据的解释。
最佳答案
是的,这是允许的。
这里有一个简单的解释。
记住 synchronized
block :
- 原子执行:
- 当另一个线程持有相同的锁时,一个线程不能进入一个
synchronized
block - 当线程进入
synchronized
block 时,它会立即看到在先前执行的synchronized
block 中所做的一切
- 当另一个线程持有相同的锁时,一个线程不能进入一个
- 以全局顺序执行,与每个线程的程序顺序一致(你说的同步顺序)
换句话说,synchronized
block 总是以全局顺序以原子方式执行。
不同的执行在 synchronized
block 如何交错方面可能不同,但情况总是如此:
threadX()
中的第一个synchronized
block 总是在第二个 block 之前执行- 对于来自
threadY()
的synchronized
block 也是如此
有 6 种可能的交错:
threadX threadY threadX threadY threadX threadY
------------------------------- ------------------------------- -------------------------------
synchronized { | synchronized { | synchronized { |
a = 1; | a = 1; | a = 1; |
} | } | } |
synchronized { | | synchronized { | synchronized {
b = 1; | | int r1 = b; | int r1 = b;
} | | } | }
| synchronized { synchronized { | | synchronized {
| int r1 = b; b = 1; | | int r2 = a;
| } } | | }
| synchronized { | synchronized { synchronized { |
| int r2 = a; | int r2 = a; b = 1 |
| } | } } | }
(Case A) (Case B) (Case C)
threadX threadY threadX threadY threadX threadY
------------------------------- ------------------------------- -------------------------------
| synchronized { | synchronized { | synchronized {
| int r1 = b; | int r1 = b; | int r1 = b;
| } | } | }
| synchronized { synchronized { | synchronized { |
| int r2 = a; a = 1; | a = 1; |
| } } | } |
synchronized { | | synchronized { synchronized { |
a = 1; | | int r2 = a; b = 1; |
} | | } } |
synchronized { | synchronized { | | synchronized {
b = 1; | b = 1; | | int r2 = a;
} | } | | }
(Case D) (Case E) (Case F)
当您在 threadY()
中合并 synchronized
block 时:
void threadY() { void threadY() {
synchronized(this) { synchronized(this) {
int r1 = b; int r1 = b;
} => int r2 = a;
synchronized(this) { }
int r2 = a; }
}
}
然后,您实际上只保留来自 threadY()
的 synchronized
block 彼此相邻的情况:即情况 A、C 和 D。
由于这次优化后没有出现新的可能执行,那么这次优化是合法的。
对于更严格和详细的解释,我建议:
- J. Manson's Ph.D. Thesis on JMM 中的“锁定粗化”一章
- 锁粗化 example在 A. Shipilev 的一篇文章中,在 the answer above 中推荐
关于java - 没有同步顺序的程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69293751/