单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式)

单例模式详解

一、单例模式的定义及应用场景

​ **单例模式是指确保一个类再任何情况下都绝对只有一个实例,并提供一个全局访问点。**单例模式是创建型模式。单例模式在现实生活中应用十分广泛。例如:J2EE标准中的ServletContext、ServletContextConfig等。

二、饿汉式单例模式

(一)、饿汉式单例模式的优缺点

饿汉式单例模式适用于单例对象较少的情况。这样可以保证绝对线程安全、执行效率比较高。

但饿汉式单例模式的缺点也是否明显,那就说所有对象类加载的时候就实例化。这样一来,如果系统中有大批量的单例对象存在,系统初始化时就会导致大量的内存浪费。即无论对象用与不用都占着空间,浪费了内存

(二)、饿汉式单例模式的代码案例

public class HungrySingleton {
    
    
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    
    
    }

    public static HungrySingleton getInstance() {
    
    
        return hungrySingleton;
    }
}

三、懒汉式单例模式

(一)、懒汉式单例模式的特点

​ 上文第二点中提及到的饿汉式内存浪费的情况,为了解决这种情况,这里提出懒汉式单例模式。

懒汉式单例模式的特点——单例对象要在被使用的时候才会初始化。

(二)、懒汉式单例模式的代码案例一

public class LazySimpleSingleton {
    
    
    private LazySimpleSingleton() {
    
    
    }

    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
    
    
        if (lazy == null) {
    
    
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

(三)、懒汉式单例模式代码案例一的缺点

​ 通过代码案例一的方式,可成功解决饿汉式的内存浪费的问题,但同时又引出了新的问题——多线程环境下,会出现线程安全问题。

1、模拟线程安全问题结果显示

(1)、线程安全出错

在这里插入图片描述

(2)、线程安全无出错

在这里插入图片描述

2、模拟线程安全问题测试代码

(1)、线程类:ExcetorThread
public class ExcetorThread implements Runnable {
    
    
    @Override
    public void run() {
    
    
        LazySimpleSingleton simpleSingleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + simpleSingleton);
    }
}
(2)、测试类:LazySimpleSingletonTest
public class LazySimpleSingletonTest {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(new ExcetorThread());
        Thread t2 = new Thread(new ExcetorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

3、模拟线程安全问题原因分析

​ 在这种方式的懒汉式单例模式中,之所以会出现线程安全问题,主要是程序运行时会出现以下3种情况

​ 1、线程1正常进行完,此时线程2才开始进行——结果显示为同一单例(无出错)

  • Debug模式下展现运行过程

    (1)、线程1还未进行创建单例对象

在这里插入图片描述

​ (2)、线程2也还未进行创建单例对象

在这里插入图片描述

​ (3)、线程1创建完单例对象

在这里插入图片描述

​ (4)、线程1打印输出结果

在这里插入图片描述

​ (5)、线程2判断单例对象是否存在——已存在,不再创建

在这里插入图片描述

​ (6)、线程1与线程2执行打印操作——输出结果为同一单例对象

在这里插入图片描述

​ 2、线程1创建完单例对象前,此时线程2已通过单例对象是否存在的判断。在线程1创建完单例对象后还没有进行打印操作,线程2再次进行创建,覆盖掉线程1所创建的单例对象——结果显示为同一单例对象(已出错)

  • Debug模式下展现运行过程

    (1)、线程1通过单例对象是否存在的判断,此时还未执行创建单例对象的语句

在这里插入图片描述

​ (2)、线程2也通过单例对象是否存在的判断,此时还未执行创建单例对象的语句

在这里插入图片描述

​ (3)、线程1创建单例对象

在这里插入图片描述

​ (4)、线程2创建单例对象——覆盖线程1所创建的单例对象

在这里插入图片描述

​ (5)、线程1与线程2执行打印操作——输出结果为同一单例对象

在这里插入图片描述

​ 3、线程1创建完单例对象前,此时线程2已通过单例对象是否存在的判断。在线程2创建单例对象前,线程1已完成创建并完成打印。线程2再次进行创建,覆盖掉线程1所创建的单例对象并完成打印操作——结果显示为不同单例对象(已出错)

  • Debug模式下展现运行过程

    (1)、线程1通过单例对象是否存在的判断,此时还未执行创建单例对象的语句

在这里插入图片描述

​ (2)、线程2也通过单例对象是否存在的判断,此时还未执行创建单例对象的语句

在这里插入图片描述

​ (3)、线程1创建单例对象

在这里插入图片描述

​ (4)、线程1输出打印结果

在这里插入图片描述

​ (5)、线程2创建单例对象——覆盖掉线程1所创建的单例对象

在这里插入图片描述

​ (6)、线程2执行打印操作——线程1与线程2打印结果不一致

在这里插入图片描述

(四)、懒汉式单例模式代码案例一的解决方法

1、解决方法的代码案例

​ 通过上锁的方式,使得获取单例对象的方法变为线程同步

public class LazySimpleSingleton {
    
    
    private LazySimpleSingleton() {
    
    
    }

    private static LazySimpleSingleton lazy = null;

    public synchronized static LazySimpleSingleton getInstance() {
    
    
        if (lazy == null) {
    
    
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

2、结果显示

​ 在线程1进入该方法后,线程2若打算再进入则进入阻塞状态。使得上文中提到的线程安全问题不再出现。

在这里插入图片描述

(五)、解决方法的改进

1、改进方法的代码案例

public class LazySimpleSingleton {
    
    
    private LazySimpleSingleton() {
    
    
    }

    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
    
    
        if (lazy == null) {
    
    
            synchronized (LazySimpleSingleton.class) {
    
    
                if (lazy == null) {
    
    
                    lazy = new LazySimpleSingleton();
                }
            }
        }
        return lazy;
    }
}

2、代码分析

​ (1)、首先通过将synchronized锁放至geiInstance()方法内

​ (2)、第一个单例对象是否为空判断,可使得单例对象已存在时不再进去锁中

​ (3)、第二个单例对象是否为空判断,避免两个或两个以上的线程通过第一个判断,重复创建单例对象,覆盖前者所创建的单例对象。

(六)、懒汉式单例模式的代码案例二——静态内部类

1、静态内部类的代码实例

public class LazyInnerClassSingleton {
    
    
    private LazyInnerClassSingleton() {
    
    
    }

    public static final LazyInnerClassSingleton getInstance() {
    
    
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
    
    
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

2、代码分析

​ (1)、在使用LazyInnerClassSingleton的时候,默认先初始化内部类

​ (2)、若无使用,则内部类不进行加载

​ (3)、静态内部类建立懒汉式单例模式的方法兼顾了饿汉式单例模式的内存浪费问题和synchronized的性能问题。内部类一定是在方法调用之前初始化,避开了线程安全问题。

(七)、上述所有单例模式构建方法的缺点以及解决方法

1、反射破坏单例测试代码

public class LazyInnerClassSingletonTest {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(null);

            c.setAccessible(true);

            Object o1 = c.newInstance();
            Object o2 = c.newInstance();

            System.out.println(o1 == o2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

2、反射破坏单例的解决方法

public class LazyInnerClassSingleton {
    
    
    private LazyInnerClassSingleton() {
    
    
        if (LazyHolder.LAZY != null) {
    
    
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    public static final LazyInnerClassSingleton getInstance() {
    
    
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
    
    
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

四、注册式单例模式

(一)、枚举式单例模式

1、枚举式单例模式代码案例
public enum EnumSingleton {
    
    
    INSTANCE;
    private Object data;

    public Object getData() {
    
    
        return data;
    }

    public void setData(Object data) {
    
    
        this.data = data;
    }

    public static EnumSingleton getInstance() {
    
    
        return INSTANCE;
    }
}
2、枚举式单例模式测试代码
public class EnumSingletonTest {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            EnumSingleton instance1 = null;

            EnumSingleton instance2 = EnumSingleton.getInstance();
            instance2.setData(new Object());

            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance1 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
            System.out.println(instance1.getData() == instance2.getData());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
3、结果显示

在这里插入图片描述

(二)、容器式单例模式

容器式单例模式适用于需要大量创建单例对象的场景,便于管理。但它是非线程安全的。

public class ContainerSingleton {
    
    
    private ContainerSingleton() {
    
    
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
    
    
        synchronized (ioc) {
    
    
            if (!ioc.containsKey(className)) {
    
    
                Object obj = null;
                try {
    
    
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
                return obj;
            } else {
    
    
                return ioc.get(className);
            }
        }
    }
}

五、设计模式的相关博客文章链接

1、七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码

链接: 七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码.

2、工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂)

链接: 工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂).

3、原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析)

链接: 原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析).

4、建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析)

链接: 建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析).

猜你喜欢

转载自blog.csdn.net/hyyyya/article/details/108553213