单例模式学习笔记

Posted gdwkong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式学习笔记相关的知识,希望对你有一定的参考价值。

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 }

以上是关于单例模式学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式学习笔记,一:单例模式

设计模式学习笔记--单例模式

设计模式学习笔记-单例模式

单例设计模式详解+源代码+JDK源码应用——Java设计模式系列学习笔记

单例模式的学习笔记

Spring的单例模式底层实现学习笔记