天天看点

设计模式-1-单例模式1、简单介绍2、几种实现单例的方式3、简单总结

单例模式

  • 1、简单介绍
  • 2、几种实现单例的方式
    • 2.1、定义私有化成员的时候,直接初始化对象(饿汉式)
    • 2.2、在静态代码块中初始化成员对象(效果同上)
    • 2.3、在使用的时候才初始化(懒汉式)
    • 2.4、使用synchronized给getInstance方法加锁(懒汉式)
    • 2.5、getInstance中使用同步代码块(懒汉式)
    • 2.6、双重检查法:在方法5的基础上再次添加一个同步代码块进行检查
    • 2.7、静态内部类方式
    • 2.8、使用枚举
  • 3、简单总结

1、简单介绍

一般应用于只需要有一个实例存在的场景,比如各种Manager 各种factory等,例如控制项目加载配置信息的对象propertiesManager等,数据库连接管理的类等
           

单例的实现方式基本上就是,将构造方法设成私有的,类中创建一个静态的私有的当前对象的成员变量,提供一个静态的公共的获取对象的方法,由该方法获取并返回私有的成员变量,下面的不同的实现方法主要体现在私有成员的初始化时机上。

单例有饿汉式和懒汉式,饿汉式,即类加载的时候就初始化成员对象,懒汉式则是在第一次使用的时候初始化。

2、几种实现单例的方式

2.1、定义私有化成员的时候,直接初始化对象(饿汉式)

public class Singleton01 {
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){}
    
    public static Singleton01 getInstance(){
        return instance;
    }
}
           

2.2、在静态代码块中初始化成员对象(效果同上)

public class Singleton02 {
    private static Singleton02 instance;
    static{
        instance = new Singleton02();
    }

    private Singleton02(){}

    public static Singleton02 getInstance(){
        return instance;
    }
}
           

2.3、在使用的时候才初始化(懒汉式)

这种方法存在线程安全问题,并发获取instance的时候,可能会得到不同的对象。

public class Singleton03 {
    private static Singleton03 instance;
    private Singleton03(){}

    public static Singleton03 getInstance(){
        if(instance == null){
            instance = new Singleton03();
        }
        return instance;
    }
}
           

2.4、使用synchronized给getInstance方法加锁(懒汉式)

解决第三种方法的线程安全问题,锁会降低效率

public class Singleton04 {
    private static Singleton04 instance;
    private Singleton04(){}

    public static synchronized Singleton04 getInstance(){
        if(instance == null){
            instance = new Singleton04();
        }
        return instance;
    }
}

           

2.5、getInstance中使用同步代码块(懒汉式)

这种方式,试图解决在线程安全的前提下提高效率,事实证明,并不能解决线程安全问题,因为判断非空这里并没有同步,有可能会出现多个线程同时进入if内部。

public class Singleton05 {
    private static Singleton05 instance;
    private Singleton05(){}

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

2.6、双重检查法:在方法5的基础上再次添加一个同步代码块进行检查

注意要加上volatile 关键字,防止java语言本地化优化的时候指令重排的时候出现问题。

public class Singleton06 {
    private static volatile Singleton06 instance;
    private Singleton06(){}
 
    public static Singleton06 getInstance(){
        if(instance == null){
            synchronized (Singleton06.class){
                if(instance == null){
                    instance = new Singleton06();
                }
            }
        }
        return instance;
    }
}
           

2.7、静态内部类方式

在单例类的内部定义一个静态内部类用来持有当前类的实例,需要实例的时候,从内部类中获取当前类的实例对象,既实现了懒加载,又是线程安全的。

当内部类没有被引用的时候,是不会被类加载器加载的,因此,不会初始化对象,只有当内部类被调用的时候,才会加载对象,实现了懒加载的效果。

public class Singleton07 {

    private Singleton07(){}
    private static class Holder{
        private static final Singleton07 instance = new Singleton07();
    }

    public static Singleton07 getInstance(){
        return Holder.instance;
    }
}
           

2.8、使用枚举

既实现了单例,保证了线程安全,还可以防止反序列化,使用时,直接Singleton08.INSTANCE即可得到一个实例。

关于枚举的详细,可以参考:

深入理解Java枚举类型(enum)

public enum Singleton08 {
    INSTANCE;
}
           

3、简单总结

3.1、饿汉式(2.1,2.2):实现方式最简单,线程安全,但是会在程序启动的时候创建对象,占用内存,如果单例对象过大,并且只在特定情况使用的话,不太推荐使用。一般情况,使用饿汉式单例实现即可。

3.2、懒汉式(2.3):可以实现需要用的时候再创建对象,但是线程不安全

3.3、加锁的懒汉式(2.4):线程安全,存在性能问题。

3.4、双重检查(2.6):线程安全,懒加载,解决性能问题,比较完美的解决方案。

3.5、静态内部类方式(2.7):线程安全,懒加载,实现方式比较简单,比较完美的解决方案。

3.6、枚举方式:线程安全,防止反序列化,完美的解决方案

线程安全: 饿汉式(2.1,2.2)、加锁懒汉式(2.4)、双重检查锁(2.6)、静态内部类(2.7)、枚举(2.8)

非线程安全: 懒汉式(2.3,2.5)

以上的模式,除了枚举方式的实现外,在反序列化的时候,都会生成新的对象,从而破坏单例结构。只有枚举方式特殊的序列化机制可以保证反序列化后得到的对象还是当前单例对象。

语法中最完美的实现方式:枚举方式(但是枚举方式实现的单例看起来很奇怪)

实际使用中使用的方式:饿汉式或者静态内部类方式