设计模式-单列模式及原型模式

760 阅读3分钟

layout: post title: 设计模式-单列模式及原型模式 categories: [设计模式, java] description: 单例模式是非常经典的高频面试题,文章主要阐述单例模式的应用场景、IDEA 环境下的多线程调试方式、保证线程安全的单例模式策略、反射暴力攻击单例解决方案及原理分析、序列化破坏单例的原理及解决方案、常见的单例模式写法等方面来全面的解析单列模式的细节。 keywords: 单例, 设计模式


简介: 单例模式是非常经典的高频面试题,文章主要阐述单例模式的应用场景、IDEA 环境下的多线程调试方式、保证线程安全的单例模式策略、反射暴力攻击单例解决方案及原理分析、序列化破坏单例的原理及解决方案、常见的单例模式写法等方面来全面的解析单列模式的细节。

1. 单列模式

1.1 单列模式定义

  • 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点

  • 隐藏其所有的构造方法

  • 单例模式是创建型模式

1.2 适用场景

  • 确保任何情况下都绝对只有一个实例

  • ServletContext 、ServletContextConfig 等;在Spring 框架应用中ApplicationContext;数据库的连接池也都是单例形式。

1.3 单例模式常见的写法

单例模式主要包含以下几种写法:

  • 饿汉式单例

  • 懒汉式单例

  • 注册式单例

  • ThreadLocal单例

饿汉式单例

饿汉式单例是在类加载时候就立刻初始化,并且创建单例对象,有着绝对的线程安全性,因为在线程还未出现之前就已经被创建了,所以不存在访问线程安全性

优点:

  • 没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好

缺点:

  • 类加载的时候就初始化,不管用与不用都占着空间,浪费了内存

代码实现:

public class HungrySingleton {

    private HungrySingleton(){

    }

    private static HungrySingleton hungrySingleton = new HungrySingleton();

    public static HungrySingleton getHungrySingleton() {
        return hungrySingleton; 

    }

}

我们也可以采用静态代码块来实现:

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungryStaticSingleton;

    static {
        hungryStaticSingleton = new HungryStaticSingleton(); 

    }

    private HungryStaticSingleton(){

    }

    public static HungryStaticSingleton getHungryStaticSingleton() {
        return hungryStaticSingleton; 

    }

}

上述两种写法都非常简单,饿汉式适用的场景一般是单例对象比较少的情况,在这基础上我们下面看一下性能更优的写法。

懒汉式单例

被外部类调用的时候内部类才会加载,会出现线程安全问题

代码实现:

实现单例类 LazySimpleSingleton

public class LazySimpleSingleton {

    private LazySimpleSingleton(){}

    private static LazySimpleSingleton lazySimpleSingleton = null;

    public static LazySimpleSingleton getLazySimpleSingleton() {
        if (lazySimpleSingleton == null){
            lazySimpleSingleton = new LazySimpleSingleton(); 
        }
        return lazySimpleSingleton; 

    }

}

我们编写线程测试类:

public class LazySimpleSingletonTest {

    public static void main(String[] args) {
        new Thread(()->{
            LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getLazySimpleSingleton(); 
            System.out.println(lazySimpleSingleton); 
        }).start(); 
        new Thread(()->{
            LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getLazySimpleSingleton(); 
            System.out.println(lazySimpleSingleton); 
        }).start(); 

    }

}

正常情况下输出的两个对象是相同的,也是单例, 但是会存在输出不同的情况,也就是说出现线程安全问题,这里我们用 IDEA 的调试功能来进一步的详细看看各个线程的执行情况

用线程模式调试,手动控制线程的执行顺序来跟踪内存的变化状态。先给线程内容打上断点:

线程内部打断点

线程0断点进入

线程0进入内部方法

切换线程1断点

线程1方法内部

线程1内部重新构建初始化

线程1重新构建初始化

两个线程返回的内容

本文由博客群发一文多发等运营工具平台 OpenWrite 发布