在什么情况下,空的同步块可以实现正确的线程语义?
2022-09-01 15:42:17
空的同步块将等到没有其他人使用该监视器。
这可能是您想要的,但是由于您尚未保护同步块中的后续代码,因此没有什么可以阻止其他人修改您在运行后续代码时等待的内容。这几乎从来都不是你想要的。
我认为前面的答案没有强调空块最有用的东西:跨线程公开变量更改和其他操作。正如 jtahlborn 所指出的,同步是通过对编译器施加内存屏障来实现此目的的。不过,我没有找到SnakeE应该在哪里讨论这个问题,所以在这里我解释了我的意思。synchronized
int variable;
void test() // This code is INCORRECT
{
new Thread( () -> // A
{
variable = 9;
for( ;; )
{
// Do other stuff
}
}).start();
new Thread( () -> // B
{
for( ;; )
{
if( variable == 9 ) System.exit( 0 );
}
}).start();
}
上面的代码不正确。编译器可能会将线程 A 对变量的更改隔离开来,从而有效地将其隐藏在 B 中,然后 B 将永远循环。
synchronized
一种更正是向变量添加修饰符。但这可能效率低下;它强制编译器公开所有更改,其中可能包括不感兴趣的中间值。另一方面,空块仅在临界点处公开更改的值。例如:volatile
synchronized
int variable;
void test() // Corrected version
{
new Thread( () -> // A
{
variable = 9;
synchronized( o ) {} // Force exposure of the change
for( ;; )
{
// Do other stuff
}
}).start();
new Thread( () -> // B
{
for( ;; )
{
synchronized( o ) {} // Look for exposed changes
if( variable == 9 ) System.exit( 0 );
}
}).start();
}
final Object o = new Object();
两个线程必须在同一对象上同步,以确保可见性。保证取决于 Java 内存模型,特别是“监视器 m 上的解锁操作与 m 上的所有后续锁定操作同步”的规则,因此发生在这些操作之前。因此,在A块的尾部解锁o的监视器发生在B块的头部的最终锁定之前。而且,由于A的写入先于其解锁,B的锁定先于其读取,因此保证扩展到涵盖写入和读取 - 写入发生 - 在读取之前 - 使修改后的程序在内存模型方面正确。synchronized
我认为这是空块最重要的用途。synchronized