Java 具有静态成员的多线程行为

Java 具有静态成员的多线程行为,java,multithreading,static,java-memory-model,Java,Multithreading,Static,Java Memory Model,对于静态成员,多线程的行为如何?与Singleton类的情况类似,如果我尝试在静态块和静态方法中创建实例,我将返回实例,两个线程将尝试同时执行getInstance()。这在内部如何表现为仅加载一次静态 public class SingleTonUsingStaticInitialization { private static SingleTonUsingStaticInitialization INSTANCE = null; static { try

对于静态成员,多线程的行为如何?与Singleton类的情况类似,如果我尝试在静态块和静态方法中创建实例,我将返回实例,两个线程将尝试同时执行getInstance()。这在内部如何表现为仅加载一次静态

public class SingleTonUsingStaticInitialization {

    private static SingleTonUsingStaticInitialization INSTANCE = null;

    static {
        try {
            INSTANCE = new SingleTonUsingStaticInitialization();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private SingleTonUsingStaticInitialization() {
    }

    public static SingleTonUsingStaticInitialization getInstance() {
        return INSTANCE;
    }
}
这个具体的例子

从螺纹角度看,这很好。就风格而言,这是可悲的。不要这样写catch块。这还意味着,如果确实发生异常(这里不能发生,因为您的构造函数为空),您的代码将向系统错误转储一半信息,然后继续,并使用Singleton实例的null引用实例—导致其他代码抛出NullPointerException(因为代码一直在运行,因为您捕获了异常,而不是让它发生)。如果您以这种方式处理所有异常,一个错误将导致日志中出现数百个错误,除了第一个错误之外,所有错误都是无关的

处理完此异常处理问题后,可以使变量
final
,并且不再为其赋值null。在处理时,使整个类
final
。它实际上已经存在(因为您只有一个私有构造函数):

当两个线程同时调用getInstance时,这一点就解决了,原因是类加载器机制本身:类加载器保证任何给定类不会被同一个加载器加载超过一次,即使两个线程同时需要(类加载器将同步/锁定以避免这种情况),初始化过程(静态块-如上面的示例所示,它是不必要的卷积)也同样受到保护,不可能发生两次

这是你得到的唯一免费软件:对于静态方法,作为一般规则,所有线程都可以同时运行同一个方法,如果他们愿意的话。在这里,他们可以这样做-只是初始化(包括
..=new Singleton();
部分)被选为只发生一次

注意:如果您必须做更复杂的事情,请创建帮助器方法:

public final class Singleton {
    private static Singleton INSTANCE = create();

    private Singleton(Data d) {
        // do stuff with 'd'
    }

    private static Singleton create() {
        Data d;
        try {
            d = readStuffFromDataIncludedInMyJar();
        } catch (IOException e) {
            throw new Error("states.txt is corrupted", e);
        }
        return new Singleton(d);
    }
}
这:

  • 保持代码简单-静态初始值设定项是一种东西,但相当奇特的java
  • 使您的代码更易于测试
  • 这是一个内部文件;如果该文件丢失/损坏,则与您的一个类文件外出散步一样可能/一样有问题。这里有一个错误是有保证的。除非您编写了一个错误或破坏了一个构建,否则这不可能发生,并且硬崩溃时会出现一个明确的异常,告诉您到底是什么错误,这正是您想要的在这种情况下,代码不会盲目地继续运行,因为磁盘驱动器崩溃或其他原因,应用程序的一半会被gobbledygook覆盖。最好的结论是一切都坏了,说出来,然后停止运行

  • 请参阅本文中的要点

    这里的代码与这里看到的代码类似,但适合使用作为单例。许多人建议使用枚举作为在Java中定义单例的理想方法

    由于在另一个答案中讨论的类加载器行为相同,因此可以保证。当类首次加载时,每个类加载器只加载一次枚举

    如果有多个线程访问此枚举定义的单个对象,则到达加载此类点的第一个线程将导致枚举对象在其构造函数方法运行时实例化。其他线程将阻止其访问枚举对象的尝试,直到枚举类完成加载它的唯一命名枚举对象完成了它的构造。自动处理所有争用,无需我们进一步编码。所有这些行为都由

    package org.vaadin.example;
    导入java.io.IOException;
    导入java.nio.file.Files;
    导入java.nio.file.Path;
    导入java.nio.file.path;
    公共枚举AppContext
    {
    实例;
    私有字符串措辞;
    AppContext()
    {
    尝试
    {
    readStuffFromDataIncludedInMyJar();
    }
    捕获(IOE异常)
    {
    抛出新错误(“未能从文本文件加载。消息#7a608ddf-8c5f-4f77-a9c9-5ab852fde5b1.”,e);
    }
    }
    私有void readStuffFromDataIncludedInMyJar()引发IOException
    {
    Path Path=Path.get(“/Users/basilbourque/example.txt”);
    String read=Files.readAllLines(path).get(0);
    这句话的意思是:;
    System.out.println(“read=“+read”);
    }
    公共静态void main(字符串[]args)
    {
    System.out.println(AppContext.INSTANCE.toString());
    }
    }
    
    跑步的时候

    read=Wazzup?
    实例
    
    您是安全的,因为
    getInstance
    将向多个线程返回同一个实例。这是由保证的,这是您应该将您的理解委托给的唯一地方。具体来说,该章说:

    对于每个类或接口C,都有一个唯一的初始化锁LC

    并进一步指出:

    初始化C的步骤如下:

    在C的初始化锁LC上进行同步。这包括等待当前线程可以获取LC

    在简单的英语中,只有一个线程可以初始化
    static
    field.Period

    释放该锁会在保证静态块中的操作与使用该静态字段的任何线程之间发生正确的。这在同一章中有暗示,从:

    一个实现可以通过在步骤1中省略锁获取(并在步骤4/5中释放)来优化该过程当它可以确定类的初始化已经完成时,前提是,就内存模型而言,所有发生在获取锁时将存在的订单之前,在执行优化时仍然存在

    public final class Singleton {
        private static Singleton INSTANCE = create();
    
        private Singleton(Data d) {
            // do stuff with 'd'
        }
    
        private static Singleton create() {
            Data d;
            try {
                d = readStuffFromDataIncludedInMyJar();
            } catch (IOException e) {
                throw new Error("states.txt is corrupted", e);
            }
            return new Singleton(d);
        }
    }