其他分享
首页 > 其他分享> > 优雅的单列模式

优雅的单列模式

作者:互联网

单列模式是java最常用的设计模式之一,可以降低资源开销,属于创建型设计模式,是一种最佳的创建对象的方式

单列模式的特点

1、单列类只能有一个实例
2、单列类必须自己创建自己的唯一实例
3、单列模式必须对外提供一个方法获取自己的实例

单列模式的有点

1、在内存中占有一个实例,减少内存的开销,尤其是频繁创建和销毁的实例
2、避免对资源的多重占用(比如写文件的操作)

单列模式的应用场景

1、网站计数器
2、应用程序的日志应用
3、数据库连接池
4、多线程的线程池设计

写出优雅的单列

饿汉式

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static SingleModel instance = new SingleModel();
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static SingleModel getInstance(){ 
		    	return instance; 
		    }
   }

以上单列的缺点:无论该对象是否会被使用,都会创建一个对象占用内存,性能方面不是很好。

懒汉式

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static SingleModel instance =null;
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static SingleModel getInstance(){ 
			    //使用的时候先判断下该对象是否存在如果不存在则创建对象
			    if(instance ==null){
			    	instance = new SingleModel();
			    }
		    return instance; 
		    }
   }

存在的问题:在多线程的情况下不安全,如果A B 两个程序同事调用到instance ==null ,那么A 和B 都会创建对象

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static SingleModel instance =null;
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static synchronized SingleModel getInstance(){ 
		    //使用的时候先判断下该对象是否存在如果不存在则创建对象
			    if(instance ==null){
			    	instance = new SingleModel();
			    }
		    	return instance; 
		    }
		   
   }

添加一个同步代码块,可以解决线程安全的问题,但是这么做会明显降低获取对象实例的效率,每次调用的时候都会在同步代码块这里排队等待,可以继续优化;

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static SingleModel instance =null;
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static SingleModel getInstance(){ 
		     //使用的时候先判断下该对象是否存在如果不存在则创建对象
		    if(instance ==null){
		    	synchronized (SingleModel .class){
		    			instance = new SingleModel();
		    	}
		    }
		   return instance; 
		   }
   }

避免了多线程创建对象时候排队的线程,先判断空,如果为空在去创建对象,但是多线程的情况下如果都执行跳过为空判断,那么也会存在多个对象的问题所以继续改良

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static SingleModel instance =null;
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static SingleModel getInstance(){ 
		     //使用的时候先判断下该对象是否存在如果不存在则创建对象
		    if(instance ==null){
		    	synchronized (SingleModel .class){
		    		//增加一次非空判断,避免重复创建对象
		    		if(instance ==null ){
		    			instance = new SingleModel();
		    		}
		    	}
		    }
		   return instance; 
		   }
   }

以上单列还不是最完美的,在多线程的情况下 private static SingleModel instance =null;还需要使用volatile来修饰,volatile的作用是防止指令重排,保证不同线程对共享变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值 对其他线程来说是立即可见的,所以下药进行一下完善

public class SingleModel {
		 //创建 SingleModel 的一个对象 
		 private static volatile  SingleModel instance =null;
		  //让构造函数为 private,这样该类就不会被实例化
		  private SingleModel(){
		  } 
		   //提供对外获取唯一可用的对象
		   public static SingleModel getInstance(){ 
		     //使用的时候先判断下该对象是否存在如果不存在则创建对象
		    if(instance ==null){
		    	synchronized (SingleModel .class){
		    		//增加一次非空判断,避免重复创建对象
		    		if(instance ==null ){
		    			instance = new SingleModel();
		    		}
		    	}
		    }
		   return instance; 
		   }
   }

首先需要明白创建对象的这个操作并不是原子性的,instance = new SingleModel();这个操作细分为三步 [ a-开辟内存空间,b-初始化对象,c-将开辟的内容空间的地址引用赋值给对象]。
在计算机执行代码的时候 存在指令重拍的现象,正常的操作a-b-c,重拍之后的实际执行顺序可能是a-c-b,当A,B 两个线程同事调用单列,A线程 进入锁中,在创建对象的时候发生了指令重排,执行的顺序为a-c-b ,当执行a-c之后,还没有执行b,这个时候cpu切换到线程B ,B进入第一次非空判断,由于instance已经有了内存指向已经不为null,但是此时的对象还有没有初始化并且返回了所有就会产生严重的错误,加上了volatile修饰之后,那么创建对象由于内存屏障的缘故禁止冲排序只能是a-b-c ,所以 避免了上诉的问题。
关于volatile以及sychronized的剖析可以参见另外一篇文章

标签:单列,创建对象,private,优雅,instance,static,模式,SingleModel,null
来源: https://blog.csdn.net/wang_chao_yang/article/details/113622265