单例设计模式

单例设计模式

 

使用单例模式的好处:

 

(1)对于频繁使用的对象,可以减轻不停的创建对象所花费的时间。

(2)由于new的次数减少,从而降低了系统内存的使用频率,同时也减轻了GC的压力,缩短了GC的停顿时间。

 

 

单例模式的不同写法

饿汉式

public class Singleton{
	private static Singleton instance=new Singleton();

	private Singleton(){
		System.out.println("创建单例");
	}
	
	public static Singleton getInstance(){
		return instance;
	}
	
	public static void open(){
		System.out.println("开始");
	}
	
	public static void main(String[] args) {
		Singleton.open();
	}
}

 

说明:这种单例实现非常简单有十分可靠,但是它唯一的不足就是无法对instance对象做延迟加载。也就是说在上述代码中,当我们执行Singleton.open()时,会输出“创建单例”,“开始”。我们不需要Singleton对象,但是在执行open()过程中时会将instance进行实例化。所以我们要延迟加载。

 

懒汉式

public class Singleton{
	private static Singleton instance=null;

	private Singleton(){
		System.out.println("创建单例");
	}

	public static synchronized Singleton getInstance(){
		if(instance==null)
			instance=new Singleton();
		return instance;
	}

	public static void open(){
		System.out.println("开始");
	}
	
	public static void main(String[] args) {
		Singleton.open();
	}
}

说明根据懒汉式,我们可以解决延迟加载的问题。但是由于在懒汉式中会出现多线程不友好的问题,所以对getInstance()方法进行了同步处理(饿汉式天生多线程友好,故不需要进行处理)。进行同步处理之后,则调用该方法耗时就长。无形中就降低了程序的性能。

 

双向校验锁的形式

public class Singleton{
	private static Singleton instance = null;

	private Singleton(){
	    System.out.println("创建单例");
	}

	public static Singleton getInstance(){
	    if(instance == null){
                synchronized(Singleton.class){
                    if(instance == null){
		        instance=new Singleton();
                    }
                }
            }
            
            return instance;
	}

	public static void open(){
		System.out.println("开始");
	}
	
	public static void main(String[] args) {
		Singleton.open();
	}
}

说明:这是懒汉式的一种表现形式,该操作不再是对整个函数进行加锁,只对存在并发问题的部分代码进行加锁。这样做的好处是改善性能。但是这样做还是会有一点不安全。

 

volatile关键字的双向校验

public class Singleton{
	private volatile static Singleton instance = null;

	private Singleton(){
	    System.out.println("创建单例");
	}

	public static Singleton getInstance(){
	    if(instance == null){
                synchronized(Singleton.class){
                    if(instance == null){
		        instance = new Singleton();
                    }
                }
            }
            
            return instance;
	}

	public static void open(){
		System.out.println("开始");
	}
	
	public static void main(String[] args) {
		Singleton.open();
	}
}

说明:这种写法更加强化了线程安全性,当声明instance引用不使用volatile时,可能线程a先执行instance = new Singleton(),但是new对象这个操作并不是原子性的,在new对象的过程分为三步,按执行顺序分别是

1.给对象分配内存空间

2.初始化实例对象

3.将引用instance指向分配的内存空间

因为处理器会优化这三个步骤,使得这三个步骤发生重排序,执行步骤变为了1->3->2。当线程执行完了3时(此时instance引用并不是null,而是指向了1步骤中分配的内存地址。但是,instance对象初始化并没有完成,并不是一个完整的对象),快要执行2步骤,突然cpu切换了资源,那么b线程就进入该方法当中,instance此时是不等于null的,所以直接执行return。此时返回的是一个不完整的对象,可能会造成系统的崩溃

 

静态内部类的形式

public class Singleton{
	
	private Singleton(){
		System.out.println("创建单例");
	}
	
	public static void open(){
		System.out.println("开始");
	}

	public static Singleton getInstance(){
		return SingletonInner.instance;
	}
	
	private static class SingletonInner{
		private static Singleton instance=new Singleton();
	}

	public static void main(String[] args) {
		Singleton.open();
	}
}

说明:在这个实现中,单例模式使用内部类的形式来维护单例的实例.当Singleton类被加载时,其内部类并不会被初始化,而调用getInstance()函数时,就会对其进行初始化.这种实现很好的兼备了上述两种实现的优点.但是这个实现仍然有例外情况,可能导致系统生成多个实例.如通过反射机制,强行调用单例类的构造方法.这种极端的方式就不做讨论.除了这些极端的方式会破坏实例的单例性,仍然有一些合法的方式对其单例性进行破坏.如串行化.下面举个例子

public class Singleton implements Serializable{
	private static Singleton instance=new Singleton();

	private Singleton(){
		System.out.println("创建单例");
	}

	public static Singleton getInstance(){
		return instance;
	}
	
	public static void open(){
		System.out.println("开始");
	}
	
//	private Object readResolve(){
//		return instance;
//	}

	public static void main(String[] args) throws Exception {
		Singleton s=Singleton.getInstance();
		//进行串行化
		FileOutputStream fos=new FileOutputStream("Singleton.txt");
		ObjectOutputStream oos=new ObjectOutputStream(fos);
		oos.writeObject(s);
		oos.flush();
		oos.close();
		//进行反串行化
		FileInputStream fis=new FileInputStream("Singleton.txt");
		ObjectInputStream ois=new ObjectInputStream(fis);
		Singleton s1=(Singleton)ois.readObject();
		
		
		System.out.println("s="+s+",s1="+s1);
	}
}                                                                                                                               

 

当注释部分代码没有写时,构建测试代码先获取instance对象,再对Singleto进行串行化和反串行化得当的对象进行比较,会发现这两个对象不相等。解决的办法是写上注释部分的代码,这样反序列化过程中readObject()方法就如同虚设,它直接使用readResolve()方法替换了原本的返回值,从而在形式上构造了单例。

 

 

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值