浅谈设计模式之单例模式

什么是单例设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。说白了,设计模式就是一些优秀的使用案例。使用设计模式可以提高代码的重用性、让代码更容易被他人理解(大家都学过,肯定好理解)、保证代码可靠性(都说了是优秀的使用案例,肯定可靠啦)。
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。
单例模式分为饿汉模式和懒汉模式。还有一种内部类单例模式。

懒汉式+饿汉式

懒汉式:都说是懒汉式了,初始化时就创建了单例对象,以后获取实例都取这个。
/**
 * 单例模式之饿汉式:不管三七二十八,初始化的时候就创建了单例对象
 */
public class Singleton1 {

	//1.将构造方法私有化,不允许外部直接创建实例
	private Singleton1(){}
	
	//2.声明类的唯一实例,使用private static实现
	private static Singleton1 instance = new Singleton1();
	
	//3.提供一个用于获取实例的方法,使用public static实现
	public static Singleton1 getInstance(){
		return instance;
	}
}

饿汉式:类初始化时不创建实例,等到真正获取实例时再去判断,只有单例对象为空时,才创建对象
/**
 * 单例模式之懒汉式:只有单例对象为空时,才创建对象
 */
public class Singleton2 {
	
	//1.将构造方法私有化,不允许外部之间创建实例
	private Singleton2(){
	}
	
	//2.声明类的唯一实例,使用private static实现
	private static Singleton2 instance;
	
	//3.提供一个用于获取实例的方法,使用public static实现
	public static Singleton2 getInstance(){
		if(instance==null){
			instance = new Singleton2();
		}
		return instance;
	}
}

如上代码所示,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字。

但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,而事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了。

因此,我们可以将synchronized关键字加在方法的内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,这样性能就有一定的提升。

/**
 *  将synchronized关键字加到获取实例方法的内部
 */
public class Singleton3 {
	
	//1.将构造方法私有化,不允许外部之间创建实例
	private Singleton3(){
	}
	//2.声明类的唯一实例,使用private static实现
	private static Singleton3 instance;
	
	//3.提供一个用于获取实例的方法,使用public static实现
	public static Singleton3 getInstance(){
		if (instance == null) {  
   			synchronized (instance) {  
       			if (instance == null) {  
       				instance = new Singleton3();  
       			}  
   			}  
 		}  		
		return instance;
	}
}
将创建和getInstance()分开,单独为创建单例的方法加synchronized关键字。这样的话我们就可以不用考虑程序的性能问题。
/**
 * 单例模式优化,创建和getInstance()分开,单独为创建加synchronized关键字 
 */
public class Singleton4 {
	//1.将构造方法私有化,不允许外部之间创建实例
	private Singleton4 (){
	}
	//2.声明类的唯一实例,使用private static实现
	private static Singleton4 instance = null;
	//3.为创建实例的方法添加同步锁
	private static synchronized void syncInit() {  
  		if (instance == null) {  
   	 		instance = new Singleton4 ();  
  		}  
   	 }
	//4.提供一个用于获取实例的方法,使用public static实现
	public static Singleton4 getInstance(){
		if (instance == null) {  
          	syncInit();  
   		}  
   		return instance;  
	}
}

内部类单例模式

上面两个汉子式的单例,也考虑了线程了,然而尽管这样,还是有可能出问题的,例如:在Java指令中创建对象和赋值操作是分开进行的,也就是说“instance = new Singleton();”语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了。

于是,我们想到使用内部类来维护单例的实现。因为JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
/**
 *  使用内部类来维护单例的实现,保证多线程情况下实例只会被初始化一次。
 */
public class Singleton5 {
	
	//1.将构造方法私有化,不允许外部之间创建实例
	private Singleton5 (){
	}
	//2.此处使用一个内部类来维护单例 
   	private static class SingletonFactory {  
  		private static Singleton5 instance = new Singleton5 ();  
   	}
	//3.声明一个使用内部类来获取单例的方法
	public static Singleton5 getInstance() {  
       	return SingletonFactory.instance;  
   	}
}

使用内部类来维护单例的例子看似很完美,但是十分完美的东西是没有的,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以,我们只能根据实际情况,选择最适合自己应用场景的实现方法。

总结

饿汉式的特点:加载类的时候比较慢,但是运行时获取实例的速度比较快,线程安全。

懒汉式的特点:加载类的时候比较快,但是运行时获取实例的速度比较慢,线程不安全。

synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值