第五篇,单例模式

单例基本概念

在当前Jvm中只会有一个该实例对象

 

单例应用场景

  1. 项目中定义的配置文件
  2. Servlet对象默认就是单例
  3. 线程池、数据库连接池
  4. Spring中Bean对象默认就是单例
  5. 实现网站计数器
  6. Jvm内置缓存框架(定义单例HashMap)
  7. 定义枚举常量信息

 

单例优缺点

 

  优点:能够节约当前堆内存,不需要频繁New对象,能够快速访问。

  缺点:当多个线程访问同一个单例对象的时候可能会存在线程安全问题。

 

单例是否可以被破解

 

在Jvm中只能存在一个实例

Jvm中可以通过哪些方式创建对象

  1. 反射机制
  2. 序列化
  3. 自己new
  4. 克隆技术


序列化概念

序列化:将我们对象转换成二进制的形式直接存放在硬盘

反序列化:从本地文件读取二进制文件,转换成对象

序列化可以创建对象 违背单例的设计原则

疑问:

 

  1. 序列化创建对象为什么没有走无参构造函数却是一个新对象
    2.防御序列化创建一个新的对象违背单例原则
readResolve

可以在序列化类中加上 readResolve方法 保证序列化生成

单例对象

原理:

通过序列化的形式创建是否会走序列化类无参构造函数

不会

判断序列化类是否有实现Serializable接口,如果实现该接口的情况下,则调用该类的父类没有实现Serializable初始化,通过父类实例化该对象。
readResolve

序列化在创建对象的时候,判断序列化类中是否存在readResolve方法,如果存在的情况下,则直接走反射调用序列化类中readResolve返回单例对象。

序列化、反射破解我们单例能不能再根本底层上解决

 

枚举 最安全单例:

  1. 先天性 序列化生成的对象默认就是保证单例
  2. 反射无法初始化到我们枚举

单例的(10种)写法

懒汉式线程不安全

public class Singleton01 {

    private static Singleton01 singleton01 = null;

  

    /**

     * 私有构造函数

     */

    private Singleton01() {

  

    }

  

    /**

     * 懒汉式 线程不安全

     *

     * @return

     */

    public static Singleton01 getInstance() {

        if (singleton01 == null) {

            singleton01 = new Singleton01();

        }

        return singleton01;

    }

  

    public static void main(String[] args) {

        Singleton01 instance1 = Singleton01.getInstance();

        Singleton01 instance2 = Singleton01.getInstance();

        System.out.println(instance1 == instance2);

    }

  

}

 

 

懒汉式:当真正需要获取对象的时候,才去创建该单例对象,该写法存在线程问题

懒汉式线程安全

public class Singleton02 {

    private static Singleton02 singleton02 = null;

  

    /**

     * 懒汉式线程安全 已经创建对象,获取该单例对象的时候还需要上锁效率比较低

     *

     * @return

     */

    public static synchronized Singleton02 getInstance() {

        if (singleton02 == null) {

            singleton02 = new Singleton02();

        }

        return singleton02;

    }

  

    public static void main(String[] args) {

        Singleton02 instance1 = Singleton02.getInstance();

        Singleton02 instance2 = Singleton02.getInstance();

        System.out.println(instance2 == instance1);

    }

}

 

 

该写法能够保证线程安全问题,获取该单例对象的时候效率非常低

懒汉式双重检验锁 (重点方法,加volatile)

public class Singleton03 {

    private static volatile Singleton03 singleton03;

  

    public static Singleton03 getInstance() {

        // 第一次检查

        if (singleton03 == null) {

            //第二次检查

            synchronized (Singleton03.class) {

                if (singleton03 == null) {

                    singleton03 = new Singleton03();

                }

            }

        }

        return singleton03;

    }

  

    public static void main(String[] args) {

        Singleton03 instance1 = Singleton03.getInstance();

        Singleton03 instance2 = Singleton03.getInstance();

        System.out.println(instance1==instance2);

    }

}

 

 

能够保证线程安全,只会创建该单例对象的时候上锁,获取该该单例对象不会上锁,效率比较高。

 

饿汉式(私有)

public class Singleton04 {

    /**

     * 提前创建单例对象,优点先天性 保证线程安全,比较占用内存

     */

    public static final Singleton04 singleton04 = new Singleton04();

  

    private Singleton04() {

  

    }

  

    private static Singleton04 getInstance() {

        return singleton04;

    }

  

    public static void main(String[] args) {

        Singleton04 instance1 = Singleton04.getInstance();

        Singleton04 instance2 = Singleton04.getInstance();

        System.out.println(instance1 == instance2);

    }

}

 

饿汉式(公有)

public class Singleton04 {

    /**

     * 提前创建单例对象,优点先天性 保证线程安全,比较占用内存

     */

    public static final Singleton04 singleton04 = new Singleton04();

  

    private Singleton04() {

  

    }

  

    private static Singleton04 getInstance() {

        return singleton04;

    }

  

    public static void main(String[] args) {

        Singleton04 instance1 = Singleton04.singleton04;

        Singleton04 instance2 = Singleton04.singleton04;

        System.out.println(instance1 == instance2);

    }

}

 

静态代码块

public class Singleton05 {

    private static Singleton05 singleton05;

  

    static {

        singleton05 = new Singleton05();

    }

  

    public static Singleton05 getInstance() {

        return singleton05;

    }

  

    public static void main(String[] args) {

        Singleton05 instance1 = Singleton05.getInstance();

        Singleton05 instance2 = Singleton05.getInstance();

        System.out.println(instance1 == instance2);

    }

}

 

 

静态内部类

public class Singleton06 {

    private Singleton06() {

        System.out.println(">>>Singleton06");

    }

  

    private static class SingletonHolder {

        private static final Singleton06 singleton06 = new Singleton06();

    }

  

    public static final Singleton06 getInstance() {

        return SingletonHolder.singleton06;

    }

  

    public static void main(String[] args) {

        Singleton06 instance1 = Singleton06.getInstance();

        Singleton06 instance2 = Singleton06.getInstance();

        System.out.println(instance1==instance2);

    }

}

 

 

 

枚举实现单例子

public enum Singleton07 {

  

  

    INSTANCE;

  

    public void getInstance() {

        System.out.println("<<>>");

    }

}

 

 

枚举最安全,不可以被反射也不能被序列化 破解

多少种方式可以创建对象

  1. 直接new对象
  2. 采用克隆对象
  3. 使用反射创建对象
  4. 序列化与反序列化

 

 

如何单例被破解

如何防止被反射破解

private Singleton01() throws Exception {

    if (singleton01 != null) {

        throw new Exception("该对象已经创建");

    }

    System.out.println("无参构造函数");

}

 

Class aClass = Class.forName("com.mayikt.Singleton01");

Constructor constructor = aClass.getDeclaredConstructor();

constructor.setAccessible(true);

Singleton01 instance02 = Singleton01.getInstance();

Singleton01 singleton01 = (Singleton01) constructor.newInstance();

System.out.println(singleton01==instance02);

 

如何防止序列化破解

 

序列化概念:将对象转换成二进制的形式直接存放在本地

反序列化概念:从硬盘读取二进制变为对象

 

 

// 1.将对象序列化存入到本地文件中

  FileOutputStream fos = new FileOutputStream("d:/code/a.txt");

ObjectOutputStream oos = new ObjectOutputStream(fos);

Singleton04 singleton1 = Singleton04.singleton04;

oos.writeObject(singleton1);

oos.close();

fos.close();

  //2.从硬盘中反序列化对象到内存中

  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));

Singleton04 singleton2 = (Singleton04) ois.readObject();

System.out.println(singleton1);

System.out.println(singleton2);

 

 

public class Singleton04 implements Serializable {

    /**

     * 提前创建单例对象,优点先天性 保证线程安全,比较占用内存

     */

    public static final Singleton04 singleton04 = new Singleton04();

  

    private Singleton04() {

        System.out.println("Singleton04");

    }

  

    private static Singleton04 getInstance() {

        return singleton04;

    }

  

    public static void main(String[] args) {

        Singleton04 instance1 = Singleton04.singleton04;

        Singleton04 instance2 = Singleton04.singleton04;

        System.out.println(instance1 == instance2);

    }

}

 

 

重写该方法 指定返回的对象 防止序列化破解

 

private Object readResolve() throws ObjectStreamException {

    return singleton04;

}

 

 

注意:如果该类是Serializable类型的 则调用它第一个非Serializable父类的无参构造函数初始化该对象该对象

 

如何反射是否可以破解单例

 

不可以被反射 也不可以被序列化破解

//         1.将对象序列化存入到本地文件中

        FileOutputStream fos = new FileOutputStream("d:/code/a.txt");

        ObjectOutputStream oos = new ObjectOutputStream(fos);

        Singleton07 singleton1 = Singleton07.INSTANCE;

        oos.writeObject(singleton1);

        oos.close();

        fos.close();

        //2.从硬盘中反序列化对象到内存中

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));

        Singleton07 singleton2 = (Singleton07) ois.readObject();

        System.out.println(singleton1==singleton2);

 

 

你可能感兴趣的:(设计模式)