单例模式学习笔记
1 单例模式(Singleton Pattern)介绍
1.1 单例模式介绍
定义:确保某一类只有一个实例,而且自行实例化并向整个系统提供这个实例。
实现:通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化。
示例代码:
例1-1
-
1 public class Singleton01 { 2 //2、创建一个对象,在类加载的时候初始 3 private static final Singleton01 singleton01 = new Singleton01(); 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton01(){ 6 7 } 8 //3、对外提供一个静态方法,以获取实例对象 9 public static Singleton01 getSingleton(){ 10 return singleton01; 11 } 12 //类中的其他方法 13 public void doSomething(){ 14 } 15 }
1.2 单例模式的应用
1.2.1 优点
- 单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁创建、销毁时,而且创建或销毁时性能无法优化,单例模式的优势就非常明显。
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
1.2.2 缺点
- 单例模式一般没有接口,扩展困难。
- 对测试不利,如果单例模式没有完成,是不能进行测试的。
1.2.3 使用场景
- 要求生成唯一序列号的环境。
- 在整个项目中需要一个共享访问点或共享数据,例如一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保存计数器的值,并确保是线程安全的。
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源。
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
- windows的任务管理器,工厂模式中的工厂....
2 单例模式的实现方式
2.1 饿汉式(线程安全,调用效率高[因为不用加锁],但是不能延时加载),如例1-1 。
2.2 懒汉式
当使用懒加载时(如例2-2-1),在高并发环境下,存在线程安全问题(图2-2-1),可能出现同时创建多个对象,需要对线程进行加锁(例2-2-2),此称为懒汉式,资源利用效率高,实现了懒加载,但是并发调用效率低,由于每次都要加载所有浪费系统资源。
例2-2-1:
-
1 public class Singleton02 { 2 //2、声明一个私有的静态变量 3 private static Singleton02 singleton02 ; 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton02(){ 6 } 7 //3、对外提供一个静态方法,以获取实例对象 8 public static Singleton02 getSingleton(){ 9 //判断singleton02是否为空,为空赋值 10 if (singleton02 == null){ 11 singleton02 = new Singleton02(); 12 } 13 return singleton02; 14 } 15 }
图2-2-1
例2-2-2:
-
1 public class Singleton02 { 2 //2、声明一个私有的静态变量 3 private static Singleton02 singleton02 ; 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton02(){ 6 } 7 //3、对外提供一个静态方法,以获取实例对象 8 public static synchronized Singleton02 getSingleton(){ 9 //判断singleton02是否为空,为空赋值 10 if (singleton02 == null){ 11 singleton02 = new Singleton02(); 12 } 13 return singleton02; 14 } 15 }
2.3 双重检测锁(由于编译器优化原因,和jvm底层模型问题,偶尔会出现问题,不建议使用)
例2-3-1
-
1 public class Singleton03 { 2 //2、声明一个私有的静态成员变量 3 private static Singleton03 singleton03; 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton03(){ 6 } 7 //3、对外提供一个静态方法,以获取实例对象 8 public static Singleton03 getSingleton(){ 9 //判断singleton03是否需要加锁 10 if (singleton03 == null){ 11 synchronized (Singleton03.class){ 12 if (singleton03 == null){ 13 singleton03 = new Singleton03(); 14 } 15 } 16 } 17 return singleton03; 18 }
图2-3-1
2.4 静态内部类(常用)
外部类没有static,所以静态内部类不会再外部类加载的时候被初始化,所以实现了懒加载
线程安全,因为实例对象是在静态内部类加载的时候创建,所以天然是单例的。
例2-4-1
-
1 public class Singleton04 { 2 3 //1、私有化构造方法,现在以new的方式创建多个对象 4 private Singleton04(){ 5 } 6 //2、创建一个静态内部类 7 private static class SingletonInstance{ 8 //静态内部类加载的时候生成单例对象 9 public static Singleton04 singleton04 = new Singleton04(); 10 } 11 //3、对外提供一个静态方法,以获取实例对象 12 public static Singleton04 getSingleton(){ 13 //当调用该方法是,静态内部类才会被加载,对象才会new出来 14 return SingletonInstance.singleton04; 15 } 16 }
2.5 枚举(线程安全,天然就单例的,能避免反射和反序列化带来的问题,但是不能懒加载)
例2-5-1
-
1 public enum Singleton05 { 2 SINGLETON_05; 3 public void doSomething(){ 4 } 5 }
1 public class Client { 2 public static void main(String[] args) { 3 Singleton05 singleton05_01 = Singleton05.SINGLETON_05; 4 Singleton05 singleton05_02 = Singleton05.SINGLETON_05; 5 Singleton05 singleton05_03 = Singleton05.SINGLETON_05; 6 System.out.println(singleton05_01);//SINGLETON_05 7 System.out.println(singleton05_02);//SINGLETON_05 8 System.out.println(singleton05_03);//SINGLETON_05 9 singleton05_01.doSomething(); 10 } 11 }
3 防止反射破解单例
3.1反射破解单例示例:
例3-1-1
1 //破解类 2 public class Client { 3 public static void main(String[] args) throws Exception { 4 5 Singleton01 singleton1 = Singleton01.getSingleton(); 6 Singleton01 singleton2 = Singleton01.getSingleton(); 7 System.out.println(singleton1 == singleton2); //true 8 9 //暴力反射破解单例 10 Class<Singleton01> clazz = (Class<Singleton01>) Class.forName("com.pri.singleton_a.Singleton01"); 11 Constructor<Singleton01> constructor = clazz.getDeclaredConstructor(null); 12 constructor.setAccessible(true); 13 Singleton01 singleton3 = constructor.newInstance(null); 14 15 System.out.println(singleton1 == singleton3); //false 16 } 17 } 18 19 //单例类 20 public class Singleton01 { 21 //2、创建一个对象,在类加载的时候初始 22 private static final Singleton01 singleton01 = new Singleton01(); 23 //1、私有化构造方法,现在以new的方式创建多个对象 24 private Singleton01(){ 25 26 } 27 //3、对外提供一个静态方法,以获取实例对象 28 public static Singleton01 getSingleton(){ 29 return singleton01; 30 } 31 //类中的其他方法 32 public void doSomething(){ 33 } 34 }
3.2 防止反射破解单例
在单例空参构造中添加判断,如
例3-2-1:
-
1 public class Singleton01 { 2 //2、创建一个对象,在类加载的时候初始 3 private static final Singleton01 singleton01 = new Singleton01(); 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton01(){ 6 if (singleton01 != null){ 7 throw new RuntimeException("已有实例,不能再调用此方法实例化"); 8 } 9 } 10 //3、对外提供一个静态方法,以获取实例对象 11 public static Singleton01 getSingleton(){ 12 return singleton01; 13 } 14 //类中的其他方法 15 public void doSomething(){ 16 } 17 } 18 19 //运行结果 :Caused by: java.lang.RuntimeException: 已有实例,不能再调用此方法实例化
4 防止反序列化破解单例
4.1 反序列化破解单例
例4-1-1
-
1 //破解类 2 public class Client { 3 public static void main(String[] args) throws Exception { 4 5 Singleton01 singleton1 = Singleton01.getSingleton(); 6 Singleton01 singleton2 = Singleton01.getSingleton(); 7 System.out.println(singleton1 == singleton2); //true 8 9 /*//1、将对象序列化到文件 10 FileOutputStream out = new FileOutputStream("singleton.txt"); 11 ObjectOutputStream oos = new ObjectOutputStream(out); 12 oos.writeObject(singleton1); 13 14 oos.close(); 15 out.close();*/ 16 17 //2、从文件中读取对象(反序列化) 18 FileInputStream input = new FileInputStream(new File("singleton.txt")); 19 20 ObjectInputStream ois = new ObjectInputStream(input); 21 Singleton01 singleton3 = (Singleton01) ois.readObject(); 22 23 System.out.println(singleton1 == singleton3); //false 24 } 25 } 26 27 //单例类 28 public class Singleton01 implements Serializable { 29 //2、创建一个对象,在类加载的时候初始 30 private static final Singleton01 singleton01 = new Singleton01(); 31 //1、私有化构造方法,现在以new的方式创建多个对象 32 private Singleton01(){ 33 if (singleton01 != null){ 34 throw new RuntimeException("已有实例,不能再调用此方法实例化"); 35 } 36 } 37 //3、对外提供一个静态方法,以获取实例对象 38 public static Singleton01 getSingleton(){ 39 return singleton01; 40 } 41 //类中的其他方法 42 public void doSomething(){ 43 } 44 }
4.2 防止反序列化破解单例
在单例类中添加一个readResolve()方法,如例4-2-1.
例4-2-1
-
1 public class Singleton01 implements Serializable { 2 //2、创建一个对象,在类加载的时候初始 3 private static final Singleton01 singleton01 = new Singleton01(); 4 //1、私有化构造方法,现在以new的方式创建多个对象 5 private Singleton01(){ 6 if (singleton01 != null){ 7 throw new RuntimeException("已有实例,不能再调用此方法实例化"); 8 } 9 } 10 //3、对外提供一个静态方法,以获取实例对象 11 public static Singleton01 getSingleton(){ 12 return singleton01; 13 } 14 //类中的其他方法 15 public void doSomething(){ 16 } 17 18 //反序列化时,直接return singleton01,不生成对象 19 private Object readResolve() throws ObjectStreamException{ 20 return singleton01; 21 } 22 }
上一篇: 工厂模式
下一篇: 一个插排引发的设计思想 (一)