一、单例模式概述
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
二、单例模式的五种写法
1.饿汉式
优点:线程安全,效率高
缺点:无法延时加载
1 public class Singleton { 2 3 private static Singleton instance = new Singleton(); 4 5 //私有化构造器 6 private Singleton() {} 7 8 //提供全局访问点 9 public static Singleton getInstance() { 10 return instance; 11 } 12 13 }
1 public class Singleton { 2 3 private static Singleton instance = null; 4 5 static { 6 instance = new Singleton(); 7 } 8 9 //私有化构造器 10 private Singleton() {} 11 12 //提供全局访问点 13 public static Singleton getInstance() { 14 return instance; 15 } 16 17 }
2. 懒汉式
优点:线程安全,延时加载
缺点:效率较低
(1)非线程安全
1 public class Singleton { 2 3 private static Singleton instance; 4 5 //私有化构造器 6 private Singleton() {} 7 8 //提供一个全局的访问点 9 public static Singleton getInstance() { 10 if (instance == null) { 11 instance = new Singleton(); 12 } 13 return instance; 14 } 15 16 }
(2)线程安全
1 public class Singleton { 2 3 private static Singleton instance; 4 5 //私有化构造器 6 private Singleton() {} 7 8 //使用同步方法获取该类对象 9 public static synchronized Singleton getInstance() { 10 if (instance == null) { 11 instance = new Singleton(); 12 } 13 return instance; 14 } 15 16 }
1 public class Singleton { 2 3 private static Singleton instance; 4 5 //私有化构造器 6 private Singleton() {} 7 8 //使用同步块获取该类对象 9 public static Singleton getInstance() { 10 synchronized (Singleton.class) { 11 if (instance == null) { 12 instance = new Singleton(); 13 } 14 return instance; 15 } 16 } 17 18 }
3.双重检查锁
注意:由于编译器优化和JVM底层内部模型原因,偶尔会出问题,不建议使用
优点:线程安全,延时加载
缺点:效率较低,会出错误
1 public class Singleton { 2 3 private static Singleton instance; 4 5 private Singleton() {} 6 7 //第一次判断是为了避免不必要的同步,第二次判断是属性为null时创建实例 8 public static Singleton getInstance() { 9 if (instance == null) { 10 synchronized (Singleton.class) { 11 if (instance == null) { 12 instance = new Singleton(); 13 } 14 } 15 } 16 return instance; 17 } 18 19 }
4.静态内部类式
不调用静态方法不加载内部类,延缓加载(懒加载),提高效率
优点:线程安全,延时加载,效率高
1 public class Singleton { 2 3 //静态内部类 4 private static class SingletonHolder { 5 //final可加可不加,因为外部类的外部无法使用该内部类 6 private static /*final*/ Singleton instance = new Singleton(); 7 } 8 9 //私有化构造器 10 private Singleton() {} 11 12 //提供一个全局的访问点 13 public static Singleton getInstance() { 14 return SingletonHolder.instance; 15 } 16 17 }
5.枚举
注意:建议使用
优点:实现简单,线程安全,效率高,由于JVM从根本上实现保障,避免反射和反序列化的漏洞
缺点:无延时加载
1 1 public enum Singleton { 2 2 //这个枚举元素,本身就是一个单例对象 3 3 INSTANCE; 4 4 }
三、破解单例模式
1.使用反序列化破解单例对象
1 import java.io.ObjectStreamException; 2 import java.io.Serializable; 3 4 /** 5 * 使用反序列化破解单例模式 6 * @author 曹磊 7 * 8 */ 9 public class Singleton implements Serializable { 10 11 private static Singleton instance; 12 13 private Singleton() {} 14 15 //使用同步方法获取该类对象 16 public static synchronized Singleton getInstance() { 17 if (instance == null) { 18 instance = new Singleton(); 19 } 20 return instance; 21 } 22 23 //反序列化时,如果创建了readResolve()方法则直接返回已经创建好的对象,而不需要再重新创建新的对象 24 private Object readResolve() throws ObjectStreamException { 25 return instance; 26 } 27 28 }
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.ObjectInputStream; 4 import java.io.ObjectOutputStream; 5 6 /** 7 * 测试反序列化破解单例模式 8 * @author 曹磊 9 * 10 */ 11 public class TestSingleton { 12 13 public static void main(String[] args) throws Exception { 14 //先获得一个对象 15 Singleton s01 = Singleton.getInstance(); 16 System.out.println("先获取的对象:" + s01); 17 18 //(1)使用序列化将对象写出到系统文件 19 String filePath = "C:\\file\\obj.txt"; 20 ObjectOutputStream oos = new ObjectOutputStream( 21 new FileOutputStream(filePath)); 22 oos.writeObject(s01); 23 oos.flush(); 24 oos.close(); 25 26 //(2)使用反序列化读取文件中的对象 27 ObjectInputStream ois = new ObjectInputStream( 28 new FileInputStream(filePath)); 29 Singleton s02 = (Singleton) ois.readObject(); 30 ois.close(); 31 32 System.out.println("反序列化读取的对象:" + s02); 33 } 34 35 }
控制台输出:
先获取的对象:[email protected]
反序列化读取的对象:[email protected]