单例类方法的并发调用

时间:2012-10-13 23:38:27

标签: 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 istance;
    }

    public void work(){
            for(int i=0; i<10000; i++){
                Log.d("-----------", ""+i);
            }
    }
}

多个线程正在调用work()函数:

public class Main {

public static void main(String[] args) {

    new Thread (new Runnable(){
        public void run(){
            Singleton s = Singleton.getSingleton();
            s.work();}
    }).start();

    System.out.println("main thread");

    new Thread(new Runnable() { 
         public void run() {
             Singleton s = Singleton.getSingleton();
                s.work();
         }
    }).start();
}
}

我注意到两个Threads同时运行,就像两个工作函数同时被实例化一样。

我想要运行最后一个线程来代替前一个线程,而不是同时运行。是否有可能在java中使第二个调用覆盖第一个调用的内存空间?

6 个答案:

答案 0 :(得分:15)

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

因此竞争条件AMY会导致创建两个实例。

最好也是最简单的是安全地懒惰地初始化单个而不用同步如下:

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

public static Singleton getSingleton() { // Note: "synchronized" not needed
    return Holder.instance;
}

这是线程安全的,因为java类加载器的契约是所有类在它们可以被使用之前完成它们的静态初始化。此外,类加载器在引用之前不会加载类。如果两个线程同时调用getSingleton()Holder类仍然只会加载一次,因此new Singleton()只会被执行一次。

这仍然是懒惰的,因为Holder类仅从<{1}}方法引用 ,因此getSingleton()类只会在第一次调用时加载Holder已成就。

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


这种代码模式是与单身一起飞行的唯一方式。它是:

  • 最快(无同步)
  • 最安全(依靠工业强度级装载机安全)
  • 最干净(最少的代码 - 双重检查锁定是丑陋的,它的作用很多行)


其他类似的代码模式(同样安全和快速)是使用单个实例的getSingleton(),但我觉得这很笨拙,意图不太明确。

答案 1 :(得分:4)

正如@amit在评论中所说,getSingleton()方法应为synchronized。这样做的原因是多个线程可能同时请求一个实例,第一个线程仍然在初始化对象,并且在下一个线程检查时引用将为null。这将导致创建两个实例。

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

将您的方法标记为synchronized将导致它阻止,并且一次只允许一个线程调用它。这应该可以解决你的问题。

答案 2 :(得分:0)

在工厂方法

上使用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
        // ...
    }
}

或者,简单地说,使用私有的最终初始化程序(实例化将在类加载时发生)

public class Singleton {
    private static final Singleton istance = new Singleton(); // class-load initialization

    private final Singleton() {} 

    public static Singleton getSingleton() { // no need for synchronized
        return istance;
    }

    public void work() { 
        // ...
    }
}

答案 3 :(得分:0)

Java Concurrency In Practice中给出的资源持有者:http://www.javaconcurrencyinpractice.com/是可用的最佳非阻塞单例模式。单例被懒惰地初始化(当第一次调用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{
}

}

答案 4 :(得分:0)

我想出了这个代码,它正在做我需要的东西。 原始问题是&#34;可以在不使用线程的情况下执行以下操作?而是用语言直接操作内存?&#34;如果答案是否定的,也许您可​​以帮助我改进以下内容:

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();
}
}

此代码对&#34; debounce&#34;非常有用。多次调用侦听器,在用户输入的爆发中触发。 它具有使用睡眠功能的缺点。睡眠时间应该足够高,以防止突发事件开始执行耗时的任务(只有最后一个事件应该)。不幸的是,即使是很长的睡眠时间,也无法保证这种情况总是会发生。

答案 5 :(得分:0)

您可以在共享资源周围使用锁定。使用Reentrant类。它可以防止多线程的竞争条件。