我最近在多线程中进行了大量的研究,因为我慢慢地将其纳入个人项目.然而,可能由于大量略有不正确的示例,在某些情况下使用同步块和波动对我来说仍然不清楚.
我的核心问题是:当线程在同步块内时,对引用和原语的更改会自动发生变化(即,在主内存而不是高速缓存上执行),或者读取也必须同步才能工作正常吗?
>如果是,同步一个简单的getter方法的目的是什么? (见示例1)此外,只要线程在任何东西上同步,都将发送到主内存的所有更改?例如,如果它被发送到一个非常高级别的同步处理工作的负载,那么每一次的改变都会变成主内存,没有什么可以缓存,直到它再次解锁?
>如果不是,必须在同步块内显式地进行更改,或者java实际上是否可以使用Lock对象? (见实施例3)
>如果同步对象需要与以任何方式更改的引用/原语相关(例如,包含它的直接对象)?我可以通过在一个对象上同步来写,如果否则安全吗? (见实施例2)
(请注意以下示例,我知道同步方法和同步(这)被皱眉和为什么,但是讨论超出了我的问题的范围)
示例1:
class Counter{ int count = 0; public synchronized void increment(){ count++; } public int getCount(){ return count; } }
在这个例子中,increment()需要同步,因为不是一个原子操作.因此,同时增加的两个线程可能导致整数增加1到计数.计数原语需要是原子的(例如不长/双/引用),这样就好了.
getCount()需要在这里同步吗?为什么?我听到的最多的解释是,我不能保证返回的计数是否是增量之前或之后.然而,这似乎是一些略有不同的解释,那就是发现自己在错误的地方.我的意思是说如果我同步getCount(),那么我仍然看不到保证 – 它现在下来不知道锁定顺序,不知道实际读取是否恰好在实际写入之前/之后.
示例2:
以下示例线程安全,如果您假设通过这里没有显示的诡计,这些方法不会同时被调用?如果每次使用随机方法完成,然后被正确读取,或者锁必须是相同的对象,将以预期的方式计数增量? (btw我完全意识到这个例子是多么可笑,但我对理论比实践更感兴趣)
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; } }
解决方法
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
所以锁提供与同步块相同的可见性保证.
无论使用同步块还是锁,只有读写器线程与写入线程使用相同的监视器或锁时,才能保证可见性.
>您的示例1不正确:如果要查看计数的最新值,则getter也必须同步.>您的示例2是不正确的,因为它使用不同的锁来保护相同的计数.>你的例子3可以.如果吸气剂没有锁定,您可以看到较旧的计数值.事情之前是JVM保证的. JVM必须遵守指定的规则,例如通过将缓存刷新到主内存.