Java 单例类方法的并发调用

Java 单例类方法的并发调用,java,multithreading,concurrency,singleton,singleton-methods,Java,Multithreading,Concurrency,Singleton,Singleton Methods,我有一个单身班: public class Singleton { private static Singleton istance = null; private Singleton() {} public synchronized static Singleton getSingleton() { if (istance == null) istance = new Singleton(); return i

我有一个单身班:

public class Singleton {
    private static Singleton istance = null;

    private Singleton() {}

    public synchronized static Singleton getSingleton() {
        if (istance == null)
            istance = new Singleton();
        return istance;
    }

    public void work(){
            for(int i=0; i<10000; i++){
                Log.d("-----------", ""+i);
            }
    }
}
我注意到这两个线程同时运行,就好像两个工作函数同时被实例化一样


我希望最后一个线程代替上一个线程运行,而不是并发运行。在java中是否可以使第二个调用覆盖第一个调用的内存空间?

正如@amit在注释中所述,您的
getSingleton()
方法应该是
同步的。原因是多个线程可能同时请求一个实例,而第一个线程仍将初始化对象,并且在下一个线程检查时引用将为null。这将导致创建两个实例

public static synchronized Singleton getSingleton() {
    if (istance == null)
        istance = new Singleton();
    return istance;
}

将方法标记为“已同步”
将导致它阻塞,并且一次只允许一个线程调用它。这将解决您的问题。

在factory方法上使用
synchronized

public class Singleton {
    private static Singleton istance = null;

    private final Singleton() {} // avoid overrides

    public static synchronized Singleton getSingleton() {
        if (istance == null)
            istance = new Singleton();
        return istance;
    }

    public void work() { // not static, otherwise there's no need for the singleton
        // ...
    }
}
或者,简单地说,使用私有的最终初始值设定项(实例化将在类加载时发生)

您的
getSingleton()
方法正在尝试删除SINGLETON实例,但存在以下问题:

  • 对变量的访问未同步
  • 变量不是易变的
  • 你没有使用
因此,竞争条件会导致创建两个实例

最好也是最简单的方法是在不进行同步的情况下安全地延迟初始化单例,如下所示:

private static class Holder {
    static Singleton instance = new Singleton();
}

public static Singleton getSingleton() { // Note: "synchronized" not needed
    return Holder.instance;
}
这是线程安全的,因为java类加载器的约定是所有类在使用之前都完成了静态初始化。此外,类加载器在被引用之前不会加载类。如果两个线程同时调用
getSingleton()
,那么
Holder
类仍然只会加载一次,因此
newsingleton()
只会执行一次

这仍然是惰性的,因为
Holder
类仅从
getSingleton()
方法引用,因此只有在第一次调用
getSingleton()
时才会加载
Holder

不需要同步,因为此代码依赖于类装入器的内部同步,这是防弹的


此代码模式是使用单例飞行的唯一方法。它是:

  • 最快的(无同步)
  • 最安全(取决于工业强度等级装载机安全性)
  • 最干净的(最少的代码-双重检查锁定很难看,而且它的功能有很多行)


另一种类似的代码模式(同样安全和快速)是对单个实例使用
enum
,但我发现这很笨拙,目的也不太清楚。

Java Concurrency in Practice中给出的资源持有者:是可用的最佳非阻塞单例模式。singleton是延迟初始化的(当第一次调用getInstance()方法时,SingletonHolder和singleton类都在运行时加载),并且访问或方法是非阻塞的

public class SingletonFactory {

private static class SingletonHolder {
    static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
    return SingletonFactory.SingletonHolder.instance;
}

static class Singleton{
}

}

我想出了这段代码,它几乎满足了我的需要。 最初的问题是“是否可以不使用线程而直接使用语言操纵内存来执行以下操作?”如果答案是否定的,也许您可以帮助我改进以下内容:

public class Main {
private static Thread t;
public static void main(String[] args) {
    work();
    for (int i =0;i<100; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
    for (int i =0;i<100; i++);
    work();
    for (int i =0;i<500; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
}

public static void work(){
    if (t != null) t.interrupt();
    t= new Thread (new Runnable(){
            public void run(){
                // Pause for 4 seconds
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e) {
                    // We've been interrupted: no more messages.
                    return;
                }
                for(int i=0; i<10000; i++){
                    System.out.println(i);
                }
            }
            });
    t.start();
}
}
公共类主{
私有静态线程t;
公共静态void main(字符串[]args){
工作();

对于(int i=0;i您可以在共享资源周围使用锁。使用可重入的
类。它可以防止多线程的争用条件。

我不确定您的问题是什么,但是您的
getSingleton()
方法应该是
同步的
工作()
不应是静态的,否则该示例不会使senseI尝试同步,但两个线程仍同时运行删除静态两个工作()函数按顺序运行,一次一个。显然,第二个调用在第一个调用之后排队。第二个调用如何才能代替第一个调用运行?@Luky
synchronized
不会阻止两个线程同时运行:它只会序列化它们对
getSingleton()的访问
函数。换句话说,这意味着您可以确定,无论当前有多少线程正在执行,在任何时间点,最多只有一个线程将执行
getSingleton()
。在这种情况下,我们不应该删除
synchronized
吗?@jcm哦,是的-忘记删除它了(我复制粘贴了原始impl并对其进行了修改)。立即修复。Thx。@Bohemian这仍然是Singleton最快、最安全和最好的方法吗?同步对
getSingleton()
的每个调用效率不高。只需对
getSingleton()的前几个调用
应进行同步,以确保实例已正确初始化和发布。@carbolymer此问题已存在5年,且相当模糊。从未提及效率。如果只是懒洋洋地实例化一个单例,则顶部答案中提到的holder模式是绕过同步的正确方法我唯一的问题是,work()一次由一个线程调用,因为Sigleton类在内存中只创建了一个对象。对吗?不,您可以从多个线程并发调用
work()
(或
getSingleton
)。如果这样做,您显然需要确保
work()
可以安全地并发调用。好吧,但本质上,作为singleton中的类,一次只能有一个线程拥有对象的锁。因此方法可以工作()一次由一个线程使用。对吗?Singleton意味着只有一个类实例存在,而不是说它不能由多个线程并发使用。因此,正如我在上面所写的,没有任何东西可以阻止并发调用
work()
。它们是完全正交的概念。
public class SingletonFactory {

private static class SingletonHolder {
    static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
    return SingletonFactory.SingletonHolder.instance;
}

static class Singleton{
}
public class Main {
private static Thread t;
public static void main(String[] args) {
    work();
    for (int i =0;i<100; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
    for (int i =0;i<100; i++);
    work();
    for (int i =0;i<500; i++);
    System.out.println("oooooooooooooooooooooooooooooooooooooooooo");
}

public static void work(){
    if (t != null) t.interrupt();
    t= new Thread (new Runnable(){
            public void run(){
                // Pause for 4 seconds
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e) {
                    // We've been interrupted: no more messages.
                    return;
                }
                for(int i=0; i<10000; i++){
                    System.out.println(i);
                }
            }
            });
    t.start();
}
}