类图是:

java中public方法里面嵌入public_java

WHY

正如定义所说,单例模式就是整个内存模型中,只有一个实例。实例少了,内存占用就少。同时,只有一个实例,也就只需要构建一个对象,计算就少。对于构造过程中需要大量计算或者占用大量资源的对象,只创建一次,就减少了资源占用和内存占用。

HOW

饿汉式

饿汉式是最简单的一种实现,在类装载过程中,完成实例化,避免多线程问题。

实现一:静态实例参数与静态代码块
public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
    }

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

根据java的特性,饿汉式还可以变种写法,有的地方称为静态代码块方式:

public class EagerSingleton {
    private static EagerSingleton INSTANCE = null;

    static {
        INSTANCE = new EagerSingleton();
    }

    private EagerSingleton() {
    }

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

这两种方式只是在写法上的区别,优缺点没有区别,只是借助Java语言特性的不同写法,所以归为一类。

饿汉式有两个明显的缺点:

  1. 类装载过程即完成实例化,如果整个应用生命周期内,实例没有使用,也就是浪费资源了。
  2. 因为没有办法向构造函数传递不同的参数,如果需要通过个性化参数定制实例时,这种方式就不支持了。
实现二:静态内部类

针对饿汉式第一个缺点,我们可以借助静态内部类的方式,将对象实例化的时间延后。

public class EagerSingleton {
    private EagerSingleton() {
    }

    private static class EagerSingletonInstance {
        private static final EagerSingleton INSTANCE = new EagerSingleton();
    }

    public static EagerSingleton getInstance() {
        return EagerSingletonInstance.INSTANCE;
    }
}

但是,依然不能很好的解决第二个缺点,如果需要根据不同的参数实现不同的实例,可以采用下面说的懒汉式实现。

懒汉式

懒汉式比饿汉式的一个优点,就是能够在使用的时候再进行实例化。但是,馅饼总是要伴随着陷阱,懒汉式写法有更多的坑,一不小心就摔着了。

错误一:单线程实现
public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

之所以定义为单线程实现,是因为INSTANCE == null这个判断,一个线程通过这个判断,开始进行对象实例化,但是还没有实例化完成,另一个线程又来了,这个时候,对象还没有实例化,就也会开始进行实例化,造成不必要的浪费。

错误二:同步方法
public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static synchronized LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

这种方式解决了多线程的问题,但是也引入了新的性能问题:太慢。synchronized把整个方法包起来,也就是每个线程进入的时候,都需要等待其他线程结束调用,才能拿到实例,在性能敏感的场景,是比较致命的。

错误三:同步代码块之单次检查
public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

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

这种写法看似将同步代码缩小,但也缩小了多线程保障,也犯了第一种写法的错误,属于没有对多线程有基本了解写出的低级错误代码。

错误四:同步代码块之双重检查
public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
            }