单例模式详解(Singleton)

160 阅读3分钟

1.单例模式定义

保证一个类仅有一个实例,并提供一个访问它的方法

2.UML建模图:

这里写图片描述

3.懒汉式

 
//等到要使用实例的时候才会创建
public class LazySingleton {
	private static LazySingleton uniqueInstance = null;
	private String data;
	
	private LazySingleton(){
	}
	
	public static synchronized  LazySingleton getInstance(){
	    if(null == uniqueInstance){
	        uniqueInstance = new LazySingleton();
	    }
	    return uniqueInstance;
	}
	
	
	public String getData() {
	    return data;
	}
	
	public void setData(String data) {
	    this.data = data;
	}
}

4.饿汉式

//类加载的时候初始化
public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

	//构造方法
    private HungrySingleton(){
    }

    private static HungrySingleton getInstance(){
        return hungrySingleton;
    }


}

5.线程安全问题

饿汉式是线程安全的,在装载类的时候不会发生并发问题

懒汉式:

解决方法

  1. public static synchronized Singleton getInstance(){} 在多线程环境下,效率很低。
  2. 双重锁 Double Check Locking volatile在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值
//volatile
private volatile static Singleton uniqueInstance = null;
private Singleton(){}
public static Singleton getInstance(){
     if(uniqueInstance == null){
	      //同步块
          synchronized(Singleton.class){
               if(uniqueInstance == null){
                    uniqueInstance = new Singleton();
		       }
		  }
     }
     return uniqueInstance;
}
  1. 内部类
private static class SingletonHolder{
     private static Singleton instance = new Singleton();
}

private Singleton(){}
public static Singleton getInstance(){
     return SingletonHolder.instance;
}
  1. 枚举类
public enum Singleton{
	UNIQUE_INSTANCE;
    public void singletonOperation(){}
 }

6.真的只有一个对象么

其实,单例模式并不能保证实例的唯一性,只要我们想办法的话,还是可以打破这种唯一性的。以下几种方法都能实现。

  1. 使用反射,虽然构造器(newInstance)为非公开,但是在反射面前就不起作用了。
//通过反射来破坏唯一性
Class<IvoryTower> classTower = IvoryTower.class;
Constructor<IvoryTower> constructorTower = classTower.getDeclaredConstructor();
constructorTower.setAccessible(true);
IvoryTower tower2 = constructorTower.newInstance();
  1. 如果单例的类实现了cloneable,那么还是可以拷贝出多个实例的。 参考链接
//IvoryTower实现Cloneable接口,并重写clone方法
IvoryTower tower3 = (IvoryTower)tower1.clone();
  1. Java中的对象序列化也有可能导致创建多个实例。避免使用readObject方法。因为通过序列化来生成对象时,无需调用构造器,读出的对象和写入的对象不相等(==)。这种单例的情况可以通过readResolve来解决,替换readObject读到的对象。
//通过序列化 IvoryTower实现Serializable
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(tower1);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
IvoryTower tower4 = (IvoryTower)ois.readObject();
System.out.println("tower4 = " + tower4);
ois.close();
  1. 使用多个类加载器加载单例类,也会导致创建多个实例

7. 单例的扩展--只生成3个实例

/**
 * 扩展单例模式,控制实际产生实例数目为 3 个
 */
public class ThreeSingleton {
	//为后面使用的 key 定义一个前缀
	 private final static String DEFAULT_KEY = "cache";
	 //定义缓存实例的容器
	 private static Map<String,ThreeSingleton> map = new HashMap<>();
	 private static int number = 1;//定义初始化实例数目为 1
	 private final static int NUM_MAX = 3;
 
	 private ThreeSingleton(){
	 
	 }
 
	 public static synchronized ThreeSingleton getInstance(){
		 //通过缓存理念及方式控制数量
		 String key = DEFAULT_PREKEY + number;
		 ThreeSingleton threeSingleton = map.get(key);
		 if(threeSingleton == null){
			 threeSingleton = new ThreeSingleton();
			 map.put(key, threeSingleton);
		 }
		 number++;//实例数目加 1
		 if(number > NUM_MAX){
			 number=1;
		 }
		 return threeSingleton;
	 }
 
	 public static void main(String args[]){
		 ThreeSingleton t1 = getInstance();
		 ThreeSingleton t2 = getInstance();
		 ThreeSingleton t3 = getInstance();
		 ThreeSingleton t4 = getInstance();
		 ThreeSingleton t5 = getInstance();
		 ThreeSingleton t6 = getInstance();
		 System.out.println(t1.toString());
		 System.out.println(t2.toString());
		 System.out.println(t3.toString());
		 System.out.println(t4.toString());
		 System.out.println(t5.toString());
		 System.out.println(t6.toString());
	 }
}

8. Java多线程(第六章)

如何使单例模式遇到多线程是安全的,正确的?

  1. 立即加载(饿汉模式):在调用方法前,实例已经被创建
  2. 延迟加载(懒汉模式):等到调用方法时,实例才被创建

9. 研磨设计模式

单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供一个全局唯一访问这个实例的访问点。