欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

单例模式学习笔记

程序员文章站 2022-06-28 16:56:31
本文简单介绍单例模式,并举出饿汉式、懒汉式、双重检测锁、静态内部类(常用)、枚举等方式实现单例方式,补充防止反射、反序列化破解单例的方法。 ......

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 }