单例类方法的并发调用
我有一个单身人士class:
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中使第二个调用覆盖第一个调用的内存空间?
你的getSingleton()
方法试图懒洋洋地初始化 SINGLETON实例,但它有以下问题:
- 对变量的访问不
synchronized
- 变量
volatile
- 您没有使用双重检查锁定
因此竞争条件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
类仅从 getSingleton()
方法引用,因此只有在第一次调用getSingleton()
时才会加载Holder
类。
不需要同步,因为此代码依赖于类加载器的内部同步,这是防弹。
这种代码模式是与单身一起飞行的唯一方式。 它是:
- 最快(没有同步)
- 最安全(依靠工业强度级装载机安全)
- 最干净的(最少的代码 – 双重检查锁定是丑陋的,它做了很多行)
其他类似的代码模式(同样安全和快速)是使用带有单个实例的enum
,但我发现这是笨拙的并且意图不太明确。
正如@amit在评论中所说,你的getSingleton()
方法应该是synchronized
。 这样做的原因是多个线程可能同时请求实例,第一个线程仍将初始化对象,并且在下一个线程检查时引用将为null。 这将导致创建两个实例。
public static synchronized Singleton getSingleton() { if (istance == null) istance = new Singleton(); return istance; }
将方法标记为已synchronized
将导致它阻止并且一次只允许一个线程调用它。 这应该可以解决您的问题。
在工厂方法上使用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() { // ... } }
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{ }
}
我想出了这个代码,它正在做我需要的东西。 原始问题是“可以在不使用线程的情况下执行以下操作吗?而是使用该语言直接操作内存?” 如果答案是否定的,也许您可以帮助我改进以下内容:
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(); } }
此代码对于“去抖动”对侦听器的多次调用非常有用,可以在用户输入的突发中触发。 它具有使用睡眠function的缺点。 睡眠时间应该足够高,以防止突发事件开始执行耗时的任务(只有最后一个事件应该)。 不幸的是,即使是很长的睡眠时间也无法保证这种情况总能发生。
您可以在共享资源周围使用锁定。 使用Reentrant
类。 它可以防止multithreading的竞争条件。