抱歉,这是一个很长的问题。
我最近一直在对多线程进行大量研究,因为我慢慢地将它实施到个人项目中。然而,可能由于大量稍微不正确的示例,在某些情况下使用同步块和波动性对我来说仍然有点不清楚。
我的核心问题是:当线程位于同步块内时,对引用和原语的更改是否会自动易失(即在主内存而不是缓存上执行),或者读取是否也必须同步才能工作适当地?
- 如果是这样,同步一个简单的 getter 方法的目的是什么?(参见示例 1)此外,只要线程已同步任何内容,所有更改是否都会发送到主内存?例如,如果它被派去在一个非常高级的同步内的所有地方做大量工作,那么每一次更改都会对主内存进行,并且没有任何东西可以缓存,直到它再次解锁?
- 如果不是,更改是否必须在同步块内显式进行,或者 java 是否可以实际使用例如 Lock 对象的使用?(见示例 3)
- 如果任一同步对象是否需要与正在以任何方式更改的引用/原语相关(例如,包含它的直接对象)?如果安全的话,我可以通过在一个对象上同步来写入并与另一个对象一起读取吗?(见示例 2)
(请注意以下示例,我知道 synchronized 方法和 synchronized(this) 不受欢迎以及为什么,但对此的讨论超出了我的问题范围)
示例 1:
class Counter{
int count = 0;
public synchronized void increment(){
count++;
}
public int getCount(){
return count;
}
}
在这个例子中,increment() 需要同步,因为 ++ 不是原子操作。因此,两个线程同时递增可能会导致计数整体增加 1。计数原语需要是原子的(例如不是长/双/引用),这很好。
getCount() 是否需要在这里同步,为什么?我听到最多的解释是,我无法保证返回的计数是 pre-increment 还是 post-increment。但是,这似乎是对某些稍有不同的解释,那就是发现自己在错误的地方。我的意思是,如果我要同步 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:
发生之前的关系是简单的 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;
}
}