sk.*_*sk. 25 java multithreading findbugs synchronized
我正在查看关于我的代码库的Findbugs报告,其中一个触发的模式是空synchronzied块(即synchronized (var) {}).该文件说:
与大多数人认识到的相比,空的同步块更加微妙且难以正确使用,并且空的同步块几乎不是比较少设计的解决方案更好的解决方案.
在我的情况下,它发生是因为块的内容已被注释掉,但synchronized声明仍然存在.在什么情况下空synchronized块可以实现正确的线程语义?
Pau*_*lin 18
空的同步块将等待,直到没有其他人使用该同步器.这可能是你想要的,但是因为你没有保护synchronized块中的后续代码,所以没有什么能阻止别人修改你在运行后续代码时所等待的内容.这几乎不是你想要的.
Mic*_*lan 13
前面的答案没有强调空synchronized块的最有用的东西:它们可以确保变量和跨线程的其他操作的可见性.正如jtahlborn指出的,同步在编译器上强加了一个"内存障碍",迫使它刷新并刷新其缓存.但我没有找到"SnakE讨论"的地方,所以我自己写了一个答案.
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();
}
Run Code Online (Sandbox Code Playgroud)
以上程序不正确.变量的值可以在线程A或B中本地缓存,或者两者都缓存.因此B可能永远不会读取A写入的9的值,因此可能永远循环.
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();
Run Code Online (Sandbox Code Playgroud)
两个线程必须在同一个对象上同步才能保证可见性.这种保证依赖于Java内存模型,特别是"监视器上的解锁操作与m 上的所有后续锁定操作同步 "的规则,从而在这些操作之前发生.所以A在其synchronized区块尾部的o监视器的解锁发生 - 在B随后锁定其区块的头部之前.(注意,关系的这个奇怪的尾部顺序解释了为什么主体可以为空.)还有A的写入在其解锁之前且B的锁在其读取之前,关系必须扩展以覆盖写入和读取:写入发生在读之前.正是这种关键的,扩展的关系使得修改后的程序在内存模型方面是正确的.
我认为这是空synchronized块最重要的用途.
*我说话好像是处理器缓存的问题,因为我认为这是一种有用的查看方式.事实上,正如Aleksandr Dubinsky评论的那样,"所有现代处理器都是缓存一致的.之前发生的关系更多地是关于允许编译器做什么而不是CPU.