一、单例模式定义
单例模式是指确保一个类在任何情况下都绝对只能有一个实例,并提供一个全局访问点。减少内存开销,避免对资源的多重占用。
二、饿汉式单例
1.代码示例
1 //标准写法 2 public class HungrySingleton { 3 4 private HungrySingleton(){} 5 6 private static final HungrySingleton hungrySingleton = new HungrySingleton(); 7 8 public static HungrySingleton getInstance(){ 9 return hungrySingleton; 10 } 11 } 12 13 //静态代码块写法 14 public class HungryStaticsingleton { 15 16 private HungryStaticsingleton(){} 17 18 private static final HungryStaticsingleton hungrySingleton; 19 20 static{ 21 hungrySingleton = new HungryStaticsingleton(); 22 } 23 24 private static HungryStaticsingleton getInstance(){ 25 return hungrySingleton; 26 } 27 }
2.饿汉式分析
饿汉式单例模式适用于单例对象较少的情况。可以保证绝对线程安全、执行效率比较高。其缺点也很明显,就是所有对象类加载的时候就实列化了。如果系统中有大批量的单例对象存在,系统初始化时就会导致大量的内存浪费
三、懒汉式单例
1.标准代码示例
1 public class LazySimpleSingleton { 2 3 private LazySimpleSingleton(){} 4 5 private static LazySimpleSingleton lazy = null; 6 7 public static LazySimpleSingleton getInstance(){ 8 if(lazy == null){ 9 lazy = new LazySimpleSingleton(); 10 } 11 return lazy; 12 } 13 }
这样能解决饿汉式的内存浪费问题,但是在多线程环境下会出现线程安全问题,于是便再加以改进,将getInstance()方法加上synchronized同步锁
2.解决线程安全问题
1 public class LazySimpleSingleton { 2 3 private LazySimpleSingleton(){} 4 5 private static LazySimpleSingleton lazy = null; 6 7 public synchronized static LazySimpleSingleton getInstance(){ 8 if(lazy == null){ 9 lazy = new LazySimpleSingleton(); 10 } 11 return lazy; 12 } 13 }
此方式解决了线程安全问题,但在线程数量比较多的情况下,若CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降,有没有更好的方式呢?于是便有了下面双重检查锁的方式
3.双重检查锁单例模式
1 public class LazyDoubleCheckSingleton { 2 3 private LazyDoubleCheckSingleton(){} 4 5 private volatile static LazyDoubleCheckSingleton lazy = null; 6 7 public static LazyDoubleCheckSingleton getInstance(){ 8 if(lazy == null){ 9 synchronized (LazyDoubleCheckSingleton.class){ 10 if(lazy == null){ 11 lazy = new LazyDoubleCheckSingleton(); 12 } 13 } 14 } 15 return lazy; 16 } 17 }
用到synchronized关键字总归是要加上锁,对程序性能有一定影响,有没有更好的方案?请看下面方法
4.静态内部类方式
1 public class LazyInnerClassSingleton { 2 3 private LazyInnerClassSingleton(){} 4 5 private static class LazyHolder{ 6 private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); 7 } 8 9 public static final LazyInnerClassSingleton getInstance(){ 10 return LazyHolder.LAZY; 11 } 12 }
这种方式兼顾了饿汉式的内存浪费问题和synchronized的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。这样真的能保证单例吗?
5.反射破坏单例
1 public class LazyInnerClassSingletonTest { 2 3 public static void main(String[] args) { 4 5 try { 6 Class<?> clazz = LazyInnerClassSingleton.class; 7 8 //通过反射获取私有的构造方法 9 Constructor c = clazz.getDeclaredConstructor(null); 10 //强制访问 11 c.setAccessible(true); 12 13 //暴力初始化 14 Object o1 = c.newInstance(); 15 16 //调用了两次构造方法,相当于"new"了两次,范了原则性错误 17 Object o2 = c.newInstance(); 18 19 System.out.println(o1==o2); 20 21 }catch (Exception e){ 22 e.printStackTrace(); 23 } 24 } 25 }
打印结果为false,创建了两个不同的实例,解决方法:我们可以在构造方法中加一些限制,代码如下:
1 public class LazyInnerClassSingleton { 2 3 private LazyInnerClassSingleton(){ 4 if(LazyHolder.LAZY!=null){ 5 throw new RuntimeException("不允许创建多个实例!"); 6 } 7 } 8 9 private static class LazyHolder{ 10 private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); 11 } 12 13 public static final LazyInnerClassSingleton getInstance(){ 14 return LazyHolder.LAZY; 15 } 16 }
虽然这看似完美了,但是还是有可能被破坏!
6.序列化破坏单例
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。代码示例:
1 public class SerializableSingleton implements Serializable { 2 3 private final static SerializableSingleton INSTANCE = new SerializableSingleton(); 4 5 private SerializableSingleton(){} 6 7 public static SerializableSingleton getInstance(){ 8 return INSTANCE; 9 } 10 } 11 12 public class SerializableSingletonTest { 13 14 public static void main(String[] args) { 15 SerializableSingleton s1= null; 16 SerializableSingleton s2 = SerializableSingleton.getInstance(); 17 18 FileOutputStream fos = null; 19 try { 20 fos = new FileOutputStream("SerializableSingleton.obj"); 21 ObjectOutputStream oos = new ObjectOutputStream(fos); 22 oos.writeObject(s2); 23 oos.flush(); 24 oos.close(); 25 26 FileInputStream fis = new FileInputStream("SerializableSingleton.obj"); 27 ObjectInputStream ois = new ObjectInputStream(fis); 28 s1 = (SerializableSingleton)ois.readObject(); 29 ois.close(); 30 31 System.out.println(s1); 32 System.out.println(s2); 33 System.out.println(s1==s2); 34 }catch (Exception e){ 35 e.printStackTrace(); 36 } 37 } 38 }
运行结果为false,代表反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例设计的初衷。解决方案代码如下:
1 public class SerializableSingleton implements Serializable { 2 3 private final static SerializableSingleton INSTANCE = new SerializableSingleton(); 4 5 private SerializableSingleton(){} 6 7 public static SerializableSingleton getInstance(){ 8 return INSTANCE; 9 } 10 11 private Object readResolve(){ 12 return INSTANCE; 13 } 14 }
增加了readResolve()方法。
原理:jdk源码ObjectInputStream类的readObject()方法,依次往下看,类实际上被实例化了两次,只不过如果类中有readResolve方法,就返回这个方法的返回值,而没有返回新创建的对象。
四、注册式单例模式
1.注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式
2.枚举式单例模式
1 public enum EnumSingleton { 2 INSTANCE; 3 4 private Object data; 5 6 public Object getData(){ 7 return data; 8 } 9 10 public void setData(Object data){ 11 this.data = data; 12 } 13 14 public static EnumSingleton getInstance(){ 15 return INSTANCE; 16 } 17 } 18 19 public class EnumSingletonTest { 20 21 public static void main(String[] args) { 22 try { 23 EnumSingleton instance1 = null; 24 25 EnumSingleton instance2 = EnumSingleton.getInstance(); 26 instance2.setData(new Object()); 27 FileOutputStream fos = new FileOutputStream("EnumSingleton.obj"); 28 ObjectOutputStream oos = new ObjectOutputStream(fos); 29 oos.writeObject(instance2); 30 oos.flush(); 31 oos.close(); 32 33 FileInputStream fis = new FileInputStream("EnumSingleton.obj"); 34 ObjectInputStream ois = new ObjectInputStream(fis); 35 instance1 = (EnumSingleton)ois.readObject(); 36 ois.close(); 37 38 System.out.println(instance1.getData()); 39 System.out.println(instance2.getData()); 40 System.out.println(instance1.getData()==instance2.getData()); 41 }catch (Exception e){ 42 e.printStackTrace(); 43 } 44 } 45 }
运行结果为true,反编译(Jad)后发现,枚举单例模式在静态代码块中就给INSTANCE进行了赋值,是饿汉式单例模式的体现;枚举类型通过类名和类对象类找到一个唯一的枚举对象,因此,枚举对象不可能被类加载器加载多次;反射也不能破坏枚举单例模式,会报找不到无参的构造方法,不能用反射创建枚举类型。
不适合大量创建单例对象的场景。
3.容器式单例
1 public class ContainerSingleton { 2 3 private ContainerSingleton(){} 4 5 private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); 6 7 public static Object getBean(String className){ 8 synchronized (ioc){ 9 if(!ioc.containsKey(className)){ 10 Object obj = null; 11 try { 12 obj = Class.forName(className).newInstance(); 13 ioc.put(className, obj); 14 }catch (Exception e){ 15 e.printStackTrace(); 16 } 17 return obj; 18 }else { 19 return ioc.get(className); 20 } 21 } 22 } 23 }
容器式单例适用于需要大量创建单例对象的场景,便于管理,但其是非线程安全的。