摘要
为了保证线程能够观察到对主内存的写入效果,必须在读取之前进行写入。如果写入和读取发生在不同的线程中,则需要进行同步操作。规范定义了许多不同类型的同步操作。其中一种操作是执行synchronized
语句,但也存在其他选择。
详细信息
Java语言规范writes:
两个动作可以通过happens-before关系排序。如果一个动作happens-before另一个动作,则第一个动作对第二个动作可见并且在其之前排序。
以及
更具体地说,如果两个动作共享happens-before关系,则它们不一定按照这个顺序出现,对于它们不共享happens-before关系的任何代码来说,它们可能会以不同的顺序出现。例如,在一个线程中写入与另一个线程中读取竞争的数据可能会以不同的顺序出现。
在您的情况下,您希望阅读线程解决正确的数独。也就是说,数独对象的初始化必须对阅读线程可见,因此初始化必须在阅读线程从数独中读取之前发生。
规范定义了“happens-before”的含义:
如果我们有两个动作x和y,我们写hb(x,y)表示x发生在y之前。
如果x和y是同一线程的动作,并且x在程序顺序中出现在y之前,则hb(x,y)。
从对象的构造函数的结尾到该对象的finalizer(§12.6)的开始有一个happens-before边缘。
如果一个动作x与后续的动作y同步,那么我们也有hb(x,y)。
如果hb(x,y)和hb(y,z),则hb(x,z)。
由于读取操作发生在不同的线程中而不是在终结器中,因此我们需要同步操作来确立写入先于读取。规范给出了以下详尽的同步操作列表:
在监视器m上的解锁操作与随后在m上进行的所有锁定操作同步(其中“随后”根据同步顺序定义)。
对易失性变量v(§8.3.1.4)的写入与任何线程对v的随后读取同步(其中“随后”根据同步顺序定义)。
启动线程的操作与它启动的线程中的第一个操作同步。
将默认值(零、false或null)写入每个变量与每个线程中的第一个操作同步。(虽然在分配包含变量的对象之前向变量写入默认值可能看起来有点奇怪,但从概念上讲,每个对象都是在程序开始时创建的,并具有其默认初始化值。)
线程T1中的最终操作与另一个线程T2中检测到T1已终止的任何操作同步(T2可以通过调用T1.isAlive()或T1.join()来完成此操作)
如果线程T1中断线程T2,则T1的中断与任何其他线程(包括T2)确定T2已被中断的任何点同步(通过抛出InterruptedException或调用Thread.interrupted或Thread.isInterrupted)。
您可以选择任何一种方法来建立happens-before关系。实际上,在数独完全构建完成后启动读取线程可能是最简单的方法。