设计模式——单例模式

原文链接:单例模式详解

什么是单例模式?

保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式

 

为什么要使用单例模式?

1、单例模式节省公共资源。比如说大家都要喝水,但没必要每家每户都打一口井。

2、单例模式方便控制。比如日志管理,如果多个人同时来写日志,日志整合就会是一个难题,而单例模式只有一个人来向日志里写入信息方便控制,避免了这种多人干扰的问题出现。

单例模式限制了类实例化对象的个数只能是一个,这样不仅可以节省公共资源还能方便控制管理。比如日志管理、线程池、数据库连接池等都需要使用单例模式。

 

 

实现单例模式的方法

饿汉模式

public class Singleton {
    //先把对象创建好
    private static final Singleton singleton = new Singleton();
    private Singleton() {}
    //其他人来拿的时候直接返回已创建好的对象
    public static Singleton getInstance() {
        return singleton;
    }
}

 

优点:简便,且线程安全

缺点:类的实例化对象如果一直不被调用就会造成资源的浪费

懒汉模式

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {
    }
    //获取对象的时候再进行实例化
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

优点:避免了资源的浪费

缺点:线程不安全,并发模式下不同线程同一时间调用的时候会创建多个类的实例。需要上锁才能实现单例。

 

解决方案1:对整个判断过程加锁

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    //获取对象的时候再进行实例化
    public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

优点:避免资源浪费,线程安全

缺点:加锁之后严重影响了性能。

 

解决方案2:双重检查加锁(DCL,Double Check Lock),即判断对象为null的时候才加锁

public static Singleton getInstance() {
    if (singleton == null) {//先验证对象是否创建
        synchronized (Singleton.class) {//只有当对象未创建的时候才上锁
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

优点:避免资源浪费,线程安全,高性能.

缺点:Jvm指令乱序导致多线程下可能出错。

情况分析:

线程1调用getInstance 获取对象实例,因为对象还是空未进行初始化,此时线程1会执行new Singleton()进行对象实例化,而当线程1的进行new Singleton()的时候JVM会生成三个指令。

指令1:分配对象内存。

指令2:调用构造器,初始化对象属性。

指令3:构建对象引用指向内存。

 

因为编译器会自作聪明的对指令进行优化, 指令优化后顺序会变成这样:

1、执行指令1:分配对象内存,

2、执行指令3:构建对象引用指向内存。

3、然后正好这个时候CPU 切到了线程2工作,而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了),那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。

所以在这种情况下,双检测锁定的方式会出现DCL失效的问题

 补充参考:双重检查锁(DCL)的原理与失效原因

解决方案3:静态内部类实现懒汉模式

class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHoler.singleton;
    }

    //定义静态内部类
    private static class SingletonHoler {
        //当内部类第一次访问时,创建对象实例
        private static Singleton singleton = new Singleton();
    }

    //测试
    public static void main(String[] args) {
        if (Singleton.getInstance() == Singleton.getInstance()) {
            System.out.println("same");
        }
    }
}

 

静态内部类原理:

当外部类被访问时,并不会加载内部类,所以只要不访问SingletonHoler 这个内部类, private static Singleton singleton = new Singleton() 不会实例化,这就相当于实现懒加载的效果,只有当SingletonHoler.singleton 被调用访问内部类的属性,此时才会将对象进行实例化,这样既解决了恶汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。

 深入理解单例模式:静态内部类单例原理

参考链接

单例模式详解

posted @ 2021-09-19 13:41  云墨亦白  阅读(94)  评论(0)    收藏  举报