Java 我需要将标志放入同步块中吗?
我有这样一个代码:Java 我需要将标志放入同步块中吗?,java,multithreading,concurrency,Java,Multithreading,Concurrency,我有这样一个代码: if(!flag) { synchronized(lock) { lock.wait(1000); } } if(!flag) { print("Error flag not set!"); } 以及: 我的一个朋友告诉我应该在synchronized块中放入flag=true: synchronized(lock) { flag = true; lock.notify() } 我不明白为什么。这是典型的例子吗?有人
if(!flag) {
synchronized(lock) {
lock.wait(1000);
}
}
if(!flag) { print("Error flag not set!"); }
以及:
我的一个朋友告诉我应该在synchronized块中放入flag=true:
synchronized(lock) {
flag = true;
lock.notify()
}
我不明白为什么。这是典型的例子吗?有人能解释一下吗
如果我声明我的标志为volatile,那么我就不需要将其放入同步块中?由于多个线程使用了
标志
变量,因此必须使用某种机制来确保更改的可见性。这确实是多线程中常见的模式。Java内存模型不保证其他线程会看到标志的新值
这是为了实现现代多处理器系统所采用的优化,在这些系统中,始终保持缓存一致性的成本可能非常高。内存访问通常比其他“普通”CPU操作慢几个数量级,因此现代处理器会尽可能地避免内存访问。相反,频繁访问的位置保存在小型、快速的本地处理器内存(缓存)中。仅对缓存进行更改,并在某些点刷新到主内存。这对于一个处理器来说很好,因为内存内容不会被其他方更改,所以我们保证缓存内容反映内存内容。(好吧,这太简单了,但从高级编程的角度来看,我认为这是不相关的)。问题是,一旦我们添加另一个处理器,独立地更改内存内容,这种保证就丢失了。为了缓解这个问题,设计了各种缓存一致性协议(有时是精心设计的,请参见例如)。不过,毫不奇怪,它们需要一些簿记和处理器间通信开销
另一个稍微相关的问题是写操作的原子性。基本上,即使其他线程看到了更改,也可以看到部分更改。在java中,这通常不是什么问题,因为语言规范保证了所有写操作的原子性。但是,对64位原语(long
和double
)的写入被明确地称为两个独立的32位写入:
对于Java编程语言内存模型而言,对非易失性长或双值的单次写入被视为两次单独的写入:每32位半写一次。这可能导致线程在一次写入中看到64位值的前32位,在另一次写入中看到第二个32位。()
回到有问题的代码。。。需要同步,并且synchronized
块满足需要。尽管如此,我还是发现让这样的标志变得易变更令人愉快。Net效果是相同的-可见性保证和原子写入-但它不会用小的同步的块使代码混乱。如果您正在检查和修改来自不同线程的标志,则需要至少声明它volatile
,以便线程看到更改
将检查放入同步块也会起作用
是的,它是并发中一个非常基本的东西,所以你应该确保你阅读了内存模型,发生在
和其他相关主题之前。主内存很慢。真慢。现在,CPU中的内部缓存速度快了1000倍左右。出于这个原因,现代代码试图在CPU的缓存中保存尽可能多的数据
主内存如此之慢的原因之一是它是共享的。当您更新主内存时,所有CPU内核都会收到更改通知。另一方面,缓存是每个核心的。这意味着当线程A更新标志时,它只更新自己的缓存。其他线程可能会也可能不会看到更改
有两种方法可确保将标志写入主存:
将其放入同步的块中
声明它是易变的
volatile
的优点是,对标志的任何访问都将确保主内存中标志的状态得到更新。当您在许多地方使用该标志时,请使用此选项
在您的例子中,您已经有了同步的块。但是在第一种情况下,第一个if
可能正在读取一个过时的值(即线程可能wait()
,即使该标志已经是true
)。因此,您仍然需要volatile,首先:lock.wait(1000)将在一秒钟后返回,即使另一个线程没有发送notify
第二:您的朋友是对的,在这种情况下,您共享了由不同线程访问的数据,因此访问数据时最好使用类似于代码中的锁进行保护
第三:将您的标志变量标记为volatile,以便不同的线程确保它们始终使用最后一个“writed”值
最后:我还将if(!flag)代码放入同步块->它也访问标志变量…不仅如此,而且第一个代码段在检查标志时应该始终保持锁。添加到上面的注释中,wait
应该在中,而loop我建议您阅读,看看应该与wait
@assylas一起使用的规范模式,因为您提到的javadoc没有回答这个问题。不管怎样,我知道了,在这种情况下,挥发性就足够了。所以当我的朋友走的时候,这条线停了,那条线开始了,他在胡说八道,他从来没有提到过volatile@Alex标记标志是不够的。您仍然需要使用while循环:while(!flag){synchronized(lock){lock.wait();}}
(因为wait
即使不调用notify
),也可能会被唤醒)-并且因为您确实需要输入一个同步块,您还可以删除volatile关键字并在synchronized块中包含while
:synchronized(lock){while(!flag)lock.wait();}
,这正是javadoc中建议的代码
synchronized(lock) {
flag = true;
lock.notify()
}