单例模式详解

单例模式详解

前言:单例模式在众多设计模式中是最基础的,也是最容易理解的;但是基础归基础,并不说明它不重要,Spring容器对象创建和依赖注入都用到了单例模式。

一、什么是单例模式?

定义:顾名思义,单例模式就是确保类只有一个实例,而且自行实例化并向整个系统提供这个实例。
特点

  1. 构造方法私有化;
  2. 实例化的变量引用私有化;
  3. 获取实例的方法共有。

注意

  1. 没有开放发构造方法,只对外提供一个能获取到该类实例的方法,并且保证任何调用者获取到的都是同一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例对象应做到多线程共享。(这就意味着为了避免线程安全问题,单例对象应该是无状态。)
二、实现单例模式的八种方式

首先,单例模式在广泛意义上可以被分为两类:饿汉式懒汉式;它们的区别就在于实例化时机不同,饿汉式的实例化是与类加载在同一个阶段,而懒汉式的实例化是被调用时才进行,换句话说,若程序中没有用到该实例的话,它将永远不会实例化。

饿汉式:
1、通过<静态常量>实现;【可应用】
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        return INSTANCE;
    }
    
}

优点:写法比较简单,在类加载时就完成了创建实例的操作,避免了线程同步问题。
缺点:这种方式没有达到懒加载的效果(Lazy Loading),实例一直存在,无论是否被用到都占用着内存空间。

2、通过<静态代码块>实现;【可应用】
public class Singleton {
    private static Singleton instance;
    static {
        // 此处可以加上实例的创建细节。
        instance = new Singleton();
    }

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        return instance;
    }
}

优点:相比于前一种,可以在静态代码块中追加一些自定义的细节;创建时机同样是在类加载时期完成。
缺点:与静态常量方式的缺点是一致的。

懒汉式:
3、线程不安全情况;【反例:不可用】
public class Singleton {
    private static Singleton instance;

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        if (instance == null){
            // 在实例为创建时先创建实例再返回,看似逻辑清晰但是存在线程安全问题,仅仅适用于单线程的情况。
            instance = new Singleton();
        }
        return instance;
    }
}

适用于单线程情况;在多线程环境下,如果同时两个线程都满足if条件并进入if语句,在容器中便会产生连个实例。

4、优化第3种,通过<同步方法>实现;【可用,但不推荐】
public class Singleton {
    private static Singleton instance;

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static synchronized Singleton getInstance(){
        if (instance == null){。
            instance = new Singleton();
        }
        return instance;
    }
}

与第3种的区别就是在getInstance方法上加上了synchronized修饰;
优点:解决了线程安全问题;
缺点:因为每次获取实例时都要先获取锁,这样大大的降低了性能。

5、通过<同步代码块>实现,还是存在线程安全问题;【反例:不可用】
public class Singleton {
    private static Singleton instance;

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        if (instance == null){
            // 加同步代码块的本意是,仅仅在第一次访问时需要获取锁,之后再也不用获取锁。(但是还是存在问题)
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }
}

此方法看似逻辑无误,但是还是存在线程安全问题;原因与第3种方式一致。

6、优化第5种,通过双重检测锁实现;【推荐使用】
public class Singleton {
    private static volatile Singleton instance;

    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        //第一次检测
        if (instance == null){
            synchronized (Singleton.class){
                // 第二次检测
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

通过上边应用的双重检测锁,可以避免创建多个实例。

7、通过<静态内部类>实现;【推荐使用】
public class Singleton {
    // 单例模式的所有构造方法必须为被private修饰
    private Singleton() {
    }
    // 对外提供一个获取实例的方法
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }

    public static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
}

这种方式是利用了,类加载时的同步特性和静态内部类的延迟加载特性实现的;这种方式实例的创建也是在第一此调用时,即第一次调用时才会加载静态内部类。

8、通过<枚举>实现;【推荐使用】
public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

完整的枚举单例;


public class User {
    //私有化构造函数
    private User(){ }
 
    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数
        private SingletonEnum(){
            user=new User();
        }
        public User getInstnce(){
            return user;
        }
    }
 
    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

public class Test {
    public static void main(String [] args){
        System.out.println(User.getInstance());
        System.out.println(User.getInstance());
        System.out.println(User.getInstance()==User.getInstance());
    }
}
结果为true

以上就是单例模式的8中实现,其中枚举的单例实现被称为最完美的单例实现,他能解决,反序列化和反射带来的多实例问题。

参考文献:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值