多线程中单例模式的优化

  • 2020 年 1 月 21 日
  • 筆記

单例模式

在编程中,单例模式是我们常用的一种设计模式,功能是保证在整个系统只用一个该对象的对象,具体代码如下:

public class Singleton {  	private static Singleton singleton;    	private Singleton() {  	}    	public static Singleton getInstance() {  		if (singleton == null) {  			singleton = new Singleton();  			return singleton;  		}  		return singleton;  	}  }

上面的代码我们知道并不是线程安全的,在多线程环境下,容易造成创建多个对象。 测试代码如下:

	@Test  	public void testSingleton() throws InterruptedException {  		for (int i=0;i<10;i++){  			new Thread(()->{  				Singleton.getInstance();  			}).start();  		}  		Thread.currentThread().join();  	}

运行结果如下:

创建对象  创建对象  创建对象  创建对象  创建对象  创建对象  创建对象

解决方案


对于上面的问题解决的方法有很多,比如使用加锁的方式,double检测的方式,为了验证最有方案我们把代码修改下:

public class Singleton {  	private   static Singleton singleton;    	private Singleton() {  		try {  			Thread.sleep(10);//增加创建对象的耗时  		} catch (Exception e) {    		}  	}    	public static Singleton getInstance() {  		synchronized (Singleton.class) {  			if (singleton == null) {  				singleton = new Singleton();  				return singleton;  			}  		}  		return singleton;  	}  }

测试代码

@Test  	public void testSingleton() throws InterruptedException {  		long start=System.currentTimeMillis();  		List<Thread> threadList = new ArrayList<>();  		for (int i = 0; i < 1000; i++) {  			Thread thread = new Thread(() -> {  				Singleton.getInstance();  			});  			threadList.add(thread);  			thread.start();  		}  		for (Thread t : threadList) {  			t.join();  		}  		long end=System.currentTimeMillis();  		System.out.println("运行耗时:"+(end-start));  	}

方案一:使用synchronized 关键字

public static Singleton getInstance() {  		synchronized (Singleton.class){  			if (singleton == null) {  				System.out.println("创建对象");  				singleton = new Singleton();  			}  		}  		return singleton;  	}

经过多次测试时间维持在410ms左右,下面是一次测试结果

运行耗时:410

这个虽然成功的保证了只有一个对象,但同样也会把其他的线程阻塞在创建的锁的前面,造成了性能上面的开销,如果创建一个对象的时间比较长,这个性能的开销是相当可观的。

方案二:double验证

	public static Singleton getInstance() {  		if (singleton == null) {  			synchronized (Singleton.class) {  				if (singleton == null) {  					singleton = new Singleton();  					return singleton;  				}  			}  		}  		return singleton;  	}
运行耗时:380

上面的代码虽然聪明的避开的过多线程等待的原因,但是彻底消除线程排队的现象,因为创建对象分需要耗时,这样就给其他线程提供了“可乘之机”

方案三:使用volatile共享变量 (最优方案)

共享变量是线程间同步的“轻量级锁”,彻底消除线程排队的现象,此处用于单例模式的设计,能够实现最小性能的开销:

private volatile static Singleton singleton;
运行耗时:280