『Java』设计模式之单例模式(Singleton Pattern)单例模式的八种写法

234 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

单例模式简介

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式使用getInstance() 方法获取唯一的实例,但是调用时中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化

单例模式|菜鸟教程 java单例模式_百度百科

单例模式实现思路

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

根据上面的要求,依次实现即可:

package singleton;

public class Single {
	// 自己创建、唯一实例
    private static final Single instance = new Single();

	// 外部无法调用构造器
    private Single(){}
    
	// 对外提供实例
    public static Single getInstance(){
        return instance;
    }
}

单例模式的设计

单例模式的设计需要考虑以下因素

  • 是否延迟加载:解决资源浪费问题
  • 是否多线程安全
  • 实现难易程度

单例模式的八种实现方法

饿汉式(可用)

饿汉式解决了多线程安全问题

①静态常量式

上述写法称为使用静态常量的饿汉式

package singleton.hungryMan;

public class StaticConstanceStyle {
    private static final StaticConstanceStyle INSTANCE = new StaticConstanceStyle();

    private StaticConstanceStyle(){}

    public static StaticConstanceStyle getInstance(){
        return INSTANCE;
    }
}

②静态代码块式

实例化操作还可以放在静态代码块里面

package singleton.hungryMan;

public class StaticBlockStyle {
    private static final StaticBlockStyle instance;

    static {
        instance = new StaticBlockStyle();
    }

    private StaticBlockStyle(){}

    public static StaticBlockStyle getInstance(){
        return INSTANCE;
    }
}

以上两种写法效果一致

优点:多线程安全,没有加锁、执行效率高、实现简单、常用 缺点:未实现延迟加载,如果实例从未调用会造成浪费

懒汉式(不可用/不推荐)

懒汉式解决延迟加载问题

③线程不安全式

在饿汉式的基础上修改:增加判空达到延迟加载

package singleton.lazyMan;

public class ThreadSafety {
    private static ThreadSafety instance;

    private ThreadSafety(){}

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

但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

优点:实现延迟加载、实现简单 缺点:最大的缺点就是多线程不安全

线程安全式

只要解决上面的线程不安全即可,主要是对getInstance方法做线程同步

④同步方法式

package singleton.lazyMan.ThreadUnsafety;

public class MethodSync {
    private static MethodSync instance;

    private MethodSync(){}

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

缺点:效率非常低、每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

⑤同步代码块式

package singleton.lazyMan.ThreadUnsafety;

public class BlockSync {
    private static BlockSync instance;

    private BlockSync(){}

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

缺点:线程不同步,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

⑥双重校验锁(Double-checked Locking, DCL)(推荐)

双检锁改进自懒汉式的同步代码块写法,进行两次判空,只进行一次实例化,下次直接return

JDK限定≥1.5

package singleton;

public class DCL {
	// 必须使用volatile
    private static volatile DCL instance;
    
    private DCL(){}
    
    public static DCL getInstance(){
        if (instance==null){
            synchronized (DCL.class){
                if (instance==null){
                    instance = new DCL();
                }
            }
        }
        return instance;
    }
}

优点:安全高性能

Volatile关键字的作用: 禁止进行指令的重排序

⑦静态内部类/登记式(推荐)

写法类似饿汉式,都是采用了类装载的机制来保证初始化实例时只有一个线程。

package singleton;

public class StaticInternalClass {
    private StaticInternalClass(){}

    private static class StaticInternalInstanceHolder{
        private static final StaticInternalClass INSTANCE = new StaticInternalClass();
    }

    public static final StaticInternalClass getInstance(){
        return StaticInternalInstanceHolder.INSTANCE;
    }
}

效果和双检锁一样,利用类加载机制保证初始化instance只有一个线程,这种方法即使StaticInternalClass被装载、instance也不一定被初始化(因为在静态内部类之中),只有显式调用getInstance才会实例化instance

优点:线程安全、延迟加载、效率高

⑧枚举

JDK限定≥1.5

package singleton;

public enum Enum {
    INSTANCE;
    public void hello(){
        System.out.println("Hello!");
    }
}

直接调用方法即可:

package singleton;

public class Main {
    public static void main(String[] args) {
        singleton.Enum.INSTANCE.hello();
    }
}

优点: **Effective Java 作者 Josh Bloch 提倡!**避免了多线程同步问题,能够防止反序列化重新创建新的对象。

使用总结

  • 建议用饿汉式
  • 有延迟加载需求才用登记式
  • 涉及反序列化用枚举
  • 其他特殊需求用双检锁
  • 不建议用懒汉式

JAVA中实现单例(Singleton)模式的八种方式