- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
抱歉,这个问题太长了。
我最近对多线程进行了大量研究,因为我慢慢地将它实现到个人项目中。然而,可能由于大量稍微不正确的示例,在某些情况下同步块(synchronized block)和波动率的使用对我来说仍然有点不清楚。
我的核心问题是:当线程在同步块(synchronized block)内时,对引用和基元的更改是否自动易失(即,在主内存而不是缓存上执行),或者读取是否也必须同步它能正常工作吗?
(请注意以下示例,我知道同步方法和同步(this)是不受欢迎的以及为什么,但关于它的讨论超出了我的问题范围)
示例 1:
class Counter{
int count = 0;
public synchronized void increment(){
count++;
}
public int getCount(){
return count;
}
}
在此示例中,increment() 需要同步,因为++ 不是原子操作。因此,两个线程同时递增可能导致计数总体增加 1。 count 原语需要是原子的(例如,不是 long/double/reference),这很好。
这里是否需要同步 getCount() 以及为什么?我听到最多的解释是,我无法保证返回的计数是前增量还是后增量。然而,这似乎是对一些稍微不同的东西的解释,那就是发现自己在错误的地方。我的意思是,如果我要同步 getCount(),那么我仍然看不到保证 - 现在归结为不知道锁定顺序,而不是不知道实际读取是否恰好在实际写入之前/之后。
示例 2:
如果您假设通过此处未显示的诡计永远不会同时调用这些方法中的任何一个,那么以下示例是否是线程安全的?如果每次都使用随机方法这样做,那么计数会以预期的方式增加,然后被正确读取,或者锁有是同一个对象吗? (顺便说一句,我完全意识到这个例子有多么荒谬,但我对理论比实践更感兴趣)
class Counter{
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private final Object lock3 = new Object();
int count = 0;
public void increment1(){
synchronized(lock1){
count++;
}
}
public void increment2(){
synchronized(lock2){
count++;
}
}
public int getCount(){
synchronized(lock3){
return count;
}
}
}
示例 3:
happens-before 关系只是一个 java 概念,还是内置于 JVM 中的实际事物?尽管我可以保证下一个示例在概念上发生先行关系,但如果它是一个内置的东西,java 是否足够智能以选择它?我假设它不是,但这个例子实际上是线程安全的吗?如果它是线程安全的,那么如果 getCount() 没有锁定呢?
class Counter{
private final Lock lock = new Lock();
int count = 0;
public void increment(){
lock.lock();
count++;
lock.unlock();
}
public int getCount(){
lock.lock();
int count = this.count;
lock.unlock();
return count;
}
}
最佳答案
是的,读取也必须同步。 This page说:
The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation.
[...]
An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor
同一页说:
Actions prior to "releasing" synchronizer methods such as Lock.unlock, Semaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lock
因此锁提供与同步块(synchronized block)相同的可见性保证。
无论您使用同步块(synchronized block)还是锁,只有当读取线程使用与写入线程相同的监视器或锁时才能保证可见性。
您的示例 1 不正确:如果您想查看计数的最新值,则 getter 也必须同步。
您的示例 2 不正确,因为它使用不同的锁来保护相同的计数。
您的示例 3 没问题。如果 getter 没有锁定,您会看到一个较旧的计数值。 happens-before 是由 JVM 保证的。 JVM 必须遵守指定的规则,例如将缓存刷新到主内存。
关于Java:同步操作与波动性究竟有何关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10781079/
Feel free to skip straight to TL/DR if you're not interested in details of the question 简短的序言: 我最近决定
我一直在阅读 A Tour of Go学习Go-Lang到目前为止一切顺利。 我目前在 Struct Fields类(class),这是右侧的示例代码: package main import "fm
Last time I got confused顺便说一下PowerShell急切地展开集合,基思总结了它的启发式如下: Putting the results (an array) within a
我是一名优秀的程序员,十分优秀!