小议Java中几种Singleton模式实现的优劣

本文探讨了5种Java中实现Singleton模式的方法,包括:同步关键字、降低同步粒度、静态类成员、静态内部类和双重检查锁定。分别分析了它们的优缺点,如性能开销、线程安全性和延迟初始化。最后提到了对第二种方法的改进,以避免可能的对象初始化问题。
摘要由CSDN通过智能技术生成

         本文根据自身的学习和使用,总结出了5种实现Singleton的方法,并对他们的优缺点进行了简单的总结。

       第一种:最常用型,代码如下

public class Singleton1 {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
 }

 private static Singleton1 instance=null;
 private Singleton1()
 {
  
 }
 public static synchronized Singleton1 getInstance()
 {
  if(instance==null)
  {
   instance=new Singleton1();
   System.out.println("Thread : "+Thread.currentThread().getName()+" is creating instance!");
  }
  System.out.println("Thread : "+Thread.currentThread().getName()+" getting instance!");
  return instance;
 }
}

这种实现,直接对getInstance函数增加了同步关键字,因此可以保证Instance任何情况下,只被创建一次,但是有个问题,就是每次对getInstance的调用都会去试图获取该同步锁,这就增加了程序运行开销,而且,如果第一次调用已经创建了instance之后,则后续的调用实质上已经不需要再去获取同步锁,而只需要判断instance是否为Null即可。因此,这种方法,虽然实现了同步控制,但同步的粒度太大,影响了程序运行的性能。

第二种:对第一种的改造,降低同步粒度

public class Singleton2 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	}
	<span style="color:#333333;">private static Integer  flag = 0;
</span>	private static Singleton2 instance=null;
	private Singleton2()
	{
		
	}
	public static  Singleton2 getInstance()
	{
		if(instance==null)
		{
			<span style="color:#333333;">synchronized(flag)
</span>			{
				if(instance==null)
				{
					instance=new Singleton2();
				}
			}
		}
		return instance;
	}
}


可以看出,上面这种实现,直接在getInstance函数里面增加同步块,这样的话,当第一次获取到同步锁后,instance被创建,

后面再对getInstance的调用,首先判断instance是否为null,如果非null,则无需再去获取同步锁,这样的话,就大大降低了等待获取同步锁的开销,另外,即便是有多个线程同时进入getInstance函数,并且同时判断instance为null,那么第一个获得同步锁的线程创建了instance后,并释放同步锁,第二个线程获得了同步锁,发现instance非null,则不做任何事,释放同步锁。也比第一种效率要高些,这里在网上看到有人直接用synchronized(instance)来实现同步控制是不正确的,因为synchronized()中不能对空对象进行操作,否则会抛出 NullPointerException异常。

第三种:通过静态类成员类实现

这种实现方式的理论基础是,instance的创建在类的加载中完成,而JVM中类的加载是线程互斥的,因此,可以自动保证instance只创建一次。代码如下:

public class Singleton3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new TestThread());
		
		Thread t2 = new Thread(new TestThread());
		t1.start();
		t2.start();
	}

	private static Singleton3 instance= new Singleton3();
	private Singleton3()
	{
		System.out.println("Thread : "+Thread.currentThread().getName()+" creating instance!");
	}
	public static Singleton3 getInstance()
	{
		System.out.println("Thread : "+Thread.currentThread().getName()+" getting instance!");
		return instance;
	}
}
public class TestThread implements Runnable {

	public void run()
	{
		Singleton3.getInstance();
	}
}
输出:
Thread : Thread-0 creating instance!
Thread : Thread-0 getting instance!
Thread : Thread-1 getting instance!



从输出结果可以看出,在Singleton3类的加载过程中,其static成员instance已经被创建,而且由于类的加载是线程互斥的,所以,可以保证instance只能被创建一次,

这种实现的好处是,无需由用户自己去增加同步控制来实现instance的创建,但是,缺点是,如果这里的instance对象如果是个比较大的object,则在类加载时就要对其

创建,而不是到了使用的时候才创建,会增加程序运行的性能开销。针对这种问题,我们可以采用静态内部类来实现延迟创建,这就是第四种实现方式。

第四种:静态内部类实现

 

public class Singleton4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new TestThread());
		
		Thread t2 = new Thread(new TestThread());
		t1.start();
		t2.start();
	}

	private static class SingletonFactory
	{
		private static Singleton4 instance = new Singleton4();		
	}
	private Singleton4()
	{
		System.out.println("Thread : "+Thread.currentThread().getName()+" creating instance!");
	}
	public static Singleton4 getInstance()
	{
		System.out.println("Thread : "+Thread.currentThread().getName()+" getting instance!");
		return SingletonFactory.instance;
	}
}
输出:
Thread : Thread-0 getting instance!
Thread : Thread-1 getting instance!
Thread : Thread-0 creating instance!


从输出结果可以看出,Singleton4中的SingletonFactory中的instance,并不是在类加载过程中就创建了,而是在调用getInstance中的SingletonFactory.instance才真正创建instance,这就达到了用时创建的延迟创建的目的,即,程序运行过程中,如果不用instance,则就不去创建,降低了运行开销。

第五种:对第二种的改进

public class Singleton5 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	}

	private Singleton5()
	{
	}
	
	private static Singleton5 instance=null;
	private static synchronized void createInstance()
	{
		if(instance==null)
		{
			instance = new Singleton5();
		}
	}
	public static Singleton5 getInstance()
	{
		if(instance == null)
		{
			createInstance();
		}
		return instance;
	}
}


即,将第二种方法中的同步块提出来作为一个单独的函数,之所以这样做,原因如下,试想如下情形:在第二种实现中,线程A进入了同步块,判断instance为null,执行instance=new Singleton2(),JVM为instance分配空间,并把找个空间赋值给instance,稍后某个时候才对分配的内存进行初始化,A执行完同步块,这时,线程B在等待同步块,并进入同步块,判断instance非null,然后退出并返回instance的引用,这时由于instance的内存空间还没有被JVM进行初始化,因此后面的调用就可能出现异常。而,把同步块单独提出来作为一个单独的函数,则在某种意义上就会避免这种情形的发生,因为在createInstance()函数退出时,JVM一定是已经对Instance的空间进行了初始化。这个原因是我个人的理解,稍后打算花一定时间去研究JVM对象的初始化,因为,我个人感觉JVM的对象初始化机制和代码所在块有关,第二种的同步块是一段代码,第五种实现中是个同步函数,这二者的差异可能会影响到JVM对对象初始化的时间。

 

以上是本人对singleton的理解和总结,有不当之处,还需要牛人们多多指正。

 


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值