Java 即使尝试从JVM静态初始化过程获取相同的锁,线程也不会停止
在Java并发实践手册中,它说 静态初始值设定项在类加载后的类初始化时由JVM运行 但是在类被任何线程使用之前。因为JVM在初始化[JLS 12.4.2]和 每个线程至少获取一次该锁,以确保类已加载, 静态初始化期间进行的内存写入对所有线程自动可见。”(Goetz 16.2.3) 想法1:首次解释 我首先认为这意味着JVM决定看到某个类使用静态字段,临时让所有线程尝试获取静态初始化使用的锁,如果该锁从未释放,那么它将停止所有线程,永远等待该锁 想法2:可能的解释更有意义,特别是对于示例代码的行为 如果只有在初始化了静态字段之后,JVM才让所有线程尝试获取静态初始化所使用的锁,那么这样就可以了。其他不是第一个使用静态字段的线程将很好,不会因为没有等待锁而停止。然而,我不确定情况是否如此。有人能证实想法2是正确的解释吗 最后是一个程序,它看起来像这样,并继续打印thread-0和thread-1Java 即使尝试从JVM静态初始化过程获取相同的锁,线程也不会停止,java,multithreading,concurrency,thread-safety,static-initialization,Java,Multithreading,Concurrency,Thread Safety,Static Initialization,在Java并发实践手册中,它说 静态初始值设定项在类加载后的类初始化时由JVM运行 但是在类被任何线程使用之前。因为JVM在初始化[JLS 12.4.2]和 每个线程至少获取一次该锁,以确保类已加载, 静态初始化期间进行的内存写入对所有线程自动可见。”(Goetz 16.2.3) 想法1:首次解释 我首先认为这意味着JVM决定看到某个类使用静态字段,临时让所有线程尝试获取静态初始化使用的锁,如果该锁从未释放,那么它将停止所有线程,永远等待该锁 想法2:可能的解释更有意义,特别是对于示例代码的行为
public class StaticBlockAndLineInterpretation {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> keepLooping()).start();
new Thread(() -> keepLooping()).start();
Thread.sleep(2500);
int x = AllThreadsStopper.threadStopper;
}
static void keepLooping() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println("This is thread " + Thread.currentThread().getName());
}
}
}
class AllThreadsStopper {
static int threadStopper;
static {
try {
threadStopper = haltAllThreadsAndNeverReturn();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static int haltAllThreadsAndNeverReturn() throws InterruptedException {
System.out.println("haltAllThreadsAndNeverReturn called");
new CountDownLatch(1).await();
return 0;
}
}
console output snippet:
This is thread Thread-0
This is thread Thread-1
This is thread Thread-0
This is thread Thread-1
haltAllThreadsAndNeverReturn called
This is thread Thread-0
This is thread Thread-1
This is thread Thread-0
This is thread Thread-1
This is thread Thread-0 and so forth...
当然,引用的部分是关于使用该类的线程的,否则,如果没有共享数据,就没有必要讨论线程安全性 描述作为类初始化过程的一部分获取锁,其中线程在检测到另一个线程当前正在执行初始化时必须进入阻塞状态: 对于每个类或接口
C
,都有一个唯一的初始化锁
LC
。从C
到LC
的映射由
Java虚拟机实现。初始化C
的步骤如下
如下:
C
,在初始化锁LC
上同步。这包括等待,直到当前线程可以获取LC
C
的Class
对象指示其他线程正在对C
进行初始化,则释放LC
并阻止当前线程,直到通知正在进行的初始化已完成,此时重复此步骤C
的Class
对象指示当前线程正在对C
进行初始化,则这必须是一个递归的初始化请求。释放LC
并正常完成C
的Class
对象指示C
已初始化,则无需进一步操作。释放LC
并正常完成T
将在第一次出现以下任一情况之前立即初始化:
是一个类,并创建了T
的实例T
- 调用由
声明的T
方法static
- 分配了由
声明的T
字段静态
- 使用由
声明的T
字段,该字段不是常量变量(§4.12.4)静态
是一个顶级类(§7.6),执行T
(§8.1.3)中词汇嵌套的T
语句(§14.10)assert
static void keepLooping() {
while (true) {
try {
Thread.sleep(1000);
new AllThreadsStopper();
} catch (InterruptedException e) {}
System.out.println("This is thread " + Thread.currentThread().getName());
}
}
由于创建类的实例会触发类的初始化,因此线程现在会被阻塞
为了完整性,§12.4.2。还提到:
当一个实现可以确定类的初始化已经完成时,它可以通过省略步骤1中的锁获取(并在步骤4/5中释放)来优化该过程,前提是,就内存模型而言,所有这些都发生在获取锁时将存在的排序之前,在执行优化时仍然存在
这就是Brian Goetz在书中所说的“这种技术可以与JVM的惰性类加载相结合,创建一种惰性初始化技术,它不需要在公共代码路径上进行同步”。这是非常有效的,因为一旦初始化完成,线程可以访问初始化的类而不需要同步开销。在您的示例中,在初始化从未完成的情况下,这种优化是不可能的,如果线程正在使用clas,则必须获取锁