您的位置:首页 > 其它

单例模式学习笔记

2018-02-04 15:16 274 查看
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 }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: