深入理解Java设计模式——单例模式

目录

  • 一、什么是单例模式
    • 1. 单例模式优缺点
  • 二、单例模式应用场景
  • 三、单例的七种写法
    • 1. 饿汉式
      • 1.1 优缺点
    • 2. 懒汉式(线程不安全)
    • 3. 懒汉式(线程安全)
    • 4. 双重检验锁(DCL)
    • 5. 静态内部类形式
    • 6. 枚举形式
    • 7. 使用容器管理
    • 8. 如何防止破坏单例
      • 8.1 使用反射技术破解单例
      • 8.2 如何防止被反射破解
      • 8.3 使用序列化技术破解单例
      • 8.4 如何防止被序列化技术破解

一、什么是单例模式

深入理解Java设计模式——单例模式_第1张图片

1. 单例模式优缺点

优点:
1、单例类只有一个实例
2、共享资源,全局使用
3、节省创建时间,提高性能

缺点:可能存在线程不安全的问题

二、单例模式应用场景

windows的任务管理器、网站的计数器、Web应用配置对象的读取、多线程的线程池

三、单例的七种写法

分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理、静态块初始化」

1. 饿汉式

package com.demo.singleton.v1;

/**
 * @Author: JYC
 * @Title: 饿汉式
 * @Description: TODO
 * @Date: 2022/4/20 10:11
 */
public class SingletonV1 {
    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV1 singLetonV1 = new SingletonV1();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV1() {

    }

    /**
     * 返回该对象的实例
     * @return
     */
    public static SingletonV1 getSingLetonV1() {
        return singLetonV1;
    }
}

package com.demo.singleton.v1;

/**
 * @Author: JYC
 * @Title: SingletonV1Test
 * @Description: TODO
 * @Date: 2022/4/20 10:35
 */
public class SingletonV1Test {
    public static void main(String[] args) {
        SingletonV1 instance1 = SingletonV1.getSingLetonV1();
        SingletonV1 instance2 = SingletonV1.getSingLetonV1();
        System.out.println(instance1 == instance2); // 判断是否为同一个对象
    }

}

深入理解Java设计模式——单例模式_第2张图片

1.1 优缺点

饿汉式
优点:先天性线程是安全的,当类初始化的时候就会创建该对象。
缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。

2. 懒汉式(线程不安全)

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式(线程不安全)
 * @Description: TODO
 * @Date: 2022/4/20 10:50
 */
public class SingletonV2 {
    // 懒汉式 当真正需要使用该对象的时候才会被初始化
    private static SingletonV2 singletonV2;

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV2() {

    }

    /**
     * 线程安全问题
     * 在多线程情况下,可能会被初始化多次
     * @return
     */
    public static SingletonV2 getSingletonV2() {
        // 当第一次singletonV2 等于null情况才会被初始化
        if (singletonV2 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }
}

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式,模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 10:54
 */
public class SingletonV2Test {
    public static void main(String[] args) {
//        SingletonV2 singleton1 = SingletonV2.getSingletonV2();
//        SingletonV2 singleton2 = SingletonV2.getSingletonV2();
//        System.out.println(singleton1 == singleton2); // 判断是否为同一个对象

        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV2 singleton1 = SingletonV2.getSingletonV2();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

深入理解Java设计模式——单例模式_第3张图片

3. 懒汉式(线程安全)

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式(线程安全)
 * @Description: TODO
 * @Date: 2022/4/20 10:50
 */
public class SingletonV2 {
    // 懒汉式 当真正需要使用该对象的时候才会被初始化
    private static SingletonV2 singletonV2;

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV2() {

    }

    /**
     * 创建和获取实例时上锁,可以解决线程安全问题,但是执行效率非常低
     * @return
     */
    public synchronized static SingletonV2 getSingletonV2() {
        try {
            Thread.sleep(2000);  //
        } catch (Exception e) {

        }
        // 当第一次singletonV2 等于null情况才会被初始化
        if (singletonV2 == null) {
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }
}

package com.demo.singleton.v2;

/**
 * @Author: JYC
 * @Title: 懒汉式,模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 10:54
 */
public class SingletonV2Test {
    public static void main(String[] args) {
//        SingletonV2 singleton1 = SingletonV2.getSingletonV2();
//        SingletonV2 singleton2 = SingletonV2.getSingletonV2();
//        System.out.println(singleton1 == singleton2); // 判断是否为同一个对象

        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV2 singleton1 = SingletonV2.getSingletonV2();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

深入理解Java设计模式——单例模式_第4张图片

4. 双重检验锁(DCL)

package com.demo.singleton.v3;

/**
 * @Author: JYC
 * @Title: SingletonV3
 * @Description: TODO
 * @Date: 2022/4/20 11:33
 */
public class SingletonV3 {
    private static SingletonV3 singletonV3;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢

    private SingletonV3() {

    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV3 getSingletonV3() {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV3 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV3.class) {
                if (singletonV3 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV3 = new SingletonV3();
                }
            }
        }
        return singletonV3;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v3;

/**
 * @Author: JYC
 * @Title: 模拟多线程操作
 * @Description: TODO
 * @Date: 2022/4/20 11:57
 */
public class SingletonV3Test {
    public static void main(String[] args) {
        // 如何去模拟高并发情况下,懒汉式线程安全问题
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SingletonV3 singleton1 = SingletonV3.getSingletonV3();
                    System.out.println(Thread.currentThread().getName() + "," + singleton1);
                }
            }).start();
        }
    }
}

深入理解Java设计模式——单例模式_第5张图片

5. 静态内部类形式

package com.demo.singleton.v4;

/**
 * @Author: JYC
 * @Title: SingletonV4
 * @Description: TODO
 * @Date: 2022/4/20 12:08
 */
public class SingletonV4 {
    private SingletonV4() {
        System.out.println("构造函数被初始化...");
    }

    public static SingletonV4 getInstance() {
        return SingletonV4Utils.singletonV4;
    }

    // 在类里面嵌套的
    private static class SingletonV4Utils {
        private static final SingletonV4 singletonV4 = new SingletonV4();

    }

    // 静态内部类特征:继承懒汉式和饿汉式优点,同时解决双重检验锁第一次加载慢的问题,读和写都不需要同步,效率非常高...
    public static void main(String[] args) {
        System.out.println("项目启动成功...");
        SingletonV4 instance1 = SingletonV4.getInstance();
        SingletonV4 instance2 = SingletonV4.getInstance();
        System.out.println(instance1 == instance2);
    }
}

深入理解Java设计模式——单例模式_第6张图片

6. 枚举形式

package com.demo.singleton.v7;

/**
 * @Author: JYC
 * @Title: EnumSingleton
 * @Description: TODO
 * @Date: 2022/4/20 14:29
 */
public enum EnumSingleton {
    INSTANCE;

    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破击
    public void add(){
        System.out.println("add方法...");
    }
}

package com.demo.singleton.v7;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author: JYC
 * @Title: EnumSingletonTest
 * @Description: TODO
 * @Date: 2022/4/20 14:29
 */
public class EnumSingletonTest {
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        EnumSingleton singleton1 = EnumSingleton.INSTANCE;
        EnumSingleton singleton2 = EnumSingleton.INSTANCE;
        System.out.println(singleton1 == singleton2);
        singleton1.add();

        // 通过反射破坏单例
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumSingleton singleton3 = declaredConstructor.newInstance();
        System.out.println(singleton3 == singleton1);
    }
}

深入理解Java设计模式——单例模式_第7张图片

7. 使用容器管理

8. 如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。

8.1 使用反射技术破解单例

package com.demo.singleton.v5;

/**
 * @Author: JYC
 * @Title: SingletonV5
 * @Description: TODO
 * @Date: 2022/4/20 14:47
 */
public class SingletonV5 {
    private static SingletonV5 singletonV5;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
    // 如何解决读和写都不加锁,还能保证唯一性线程安全问题

    private SingletonV5() {
        System.out.println("SingletonV5被初始化...");
    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV5 getSingletonV5() {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV5 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV5.class) {
                if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV5 = new SingletonV5();
                }
            }
        }
        return singletonV5;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v5;

import com.demo.singleton.v3.SingletonV3;

import java.lang.reflect.Constructor;

/**
 * @Author: JYC
 * @Title: SingletonV5Test
 * @Description: TODO
 * @Date: 2022/4/20 14:48
 */
public class SingletonV5Test {
    public static void main(String[] args) throws Exception {
        // 单例基本原则:保证在单个jvm中不重复创建
        SingletonV5 singleton1 = SingletonV5.getSingletonV5();
        // 如何去破解单例?  使用java的反射技术、序列化技术
        Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 使用java的反射技术创建
        SingletonV5 singleton2 = declaredConstructor.newInstance();
        System.out.println(singleton1 == singleton2);
    }
}

深入理解Java设计模式——单例模式_第8张图片

8.2 如何防止被反射破解

package com.demo.singleton.v5;

/**
 * @Author: JYC
 * @Title: SingletonV5
 * @Description: TODO
 * @Date: 2022/4/20 14:47
 */
public class SingletonV5 {
    private static SingletonV5 singletonV5;
    // 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
    // 如何解决读和写都不加锁,还能保证唯一性线程安全问题

    private SingletonV5() throws Exception {
        if (singletonV5 != null) {
            throw new Exception("对象已经被初始化");
        }
        System.out.println("SingletonV5被初始化...");
    }

    /**
     * 读的不加锁,写的时候才会加锁
     */
    public static SingletonV5 getSingletonV5() throws Exception {

        // 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
        if (singletonV5 == null) {
            try {
                Thread.sleep(2000);  //
            } catch (Exception e) {

            }
            synchronized (SingletonV5.class) {
                if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
                    singletonV5 = new SingletonV5();
                }
            }
        }
        return singletonV5;
    }
    // 双重检验锁的目的时什么?    解决懒汉式获取对象效率问题。
}

package com.demo.singleton.v5;

import com.demo.singleton.v3.SingletonV3;

import java.lang.reflect.Constructor;

/**
 * @Author: JYC
 * @Title: SingletonV5Test
 * @Description: TODO
 * @Date: 2022/4/20 14:48
 */
public class SingletonV5Test {
    public static void main(String[] args) throws Exception {
        // 单例基本原则:保证在单个jvm中不重复创建
        SingletonV5 singleton1 = SingletonV5.getSingletonV5();
        // 如何去破解单例?  使用java的反射技术、序列化技术
        Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        // 使用java的反射技术创建
        SingletonV5 singleton2 = declaredConstructor.newInstance();
        System.out.println(singleton1 == singleton2);
    }
}

深入理解Java设计模式——单例模式_第9张图片

8.3 使用序列化技术破解单例

package com.demo.singleton.v6;

import java.io.*;

/**
 * @Author: JYC
 * @Title: SingletonV6
 * @Description: TODO
 * @Date: 2022/4/20 14:20
 */
public class SingletonV6 implements Serializable {
    public static void main(String[] args) throws Exception {
        // 从硬盘中读取到内存——反序列化
        SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
        FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();

        // 反序列化
        FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonV6 singleton2 = (SingletonV6) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }

    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV6 singLetonV6 = new SingletonV6();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV6() {

    }

    /**
     * 返回该对象的实例
     *
     * @return
     */
    public static SingletonV6 getSingLetonV6() {
        return singLetonV6;
    }
}

深入理解Java设计模式——单例模式_第10张图片
深入理解Java设计模式——单例模式_第11张图片

8.4 如何防止被序列化技术破解

package com.demo.singleton.v6;

import java.io.*;

/**
 * @Author: JYC
 * @Title: SingletonV6
 * @Description: TODO
 * @Date: 2022/4/20 14:20
 */
public class SingletonV6 implements Serializable {
    public static void main(String[] args) throws Exception {
        // 从硬盘中读取到内存——反序列化
        SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
        FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton1);
        oos.flush();
        oos.close();

        // 反序列化
        FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonV6 singleton2 = (SingletonV6) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }

    /**
     * 饿汉式
     * 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
     * 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
     * 如果用户不使用该对象的时候,也会被提前创建好
     */
    private static SingletonV6 singLetonV6 = new SingletonV6();

    // 1. 单例模式是否可以让程序员初始化
    private SingletonV6() {

    }

    /**
     * 返回该对象的实例
     *
     * @return
     */
    public static SingletonV6 getSingLetonV6() {
        return singLetonV6;
    }

    // 返回序列化获取对象,保证为单例
    public Object readResolve() {
        return singLetonV6;
    }
}

深入理解Java设计模式——单例模式_第12张图片

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