Singleton模式即单例模式,故名思意,单例模式会确保任何情况下都绝对只有一个实例,当我们想在程序中表示某个东西只会存在一个的时候,那么就需要单例模式。

示例程序

Singleton 类

public class Singleton {
    private static Singleton singleton = new Singleton();

    // 构造器是私有的,无法使用new生成实例
    private Singleton() {
        System.out.println("生成了一个实例");
    }

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

Main 类

public class Main {
    public static void main(String[] args){
        Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();
        if(obj1 == obj2){
            System.out.println("obj1 与 obj2 是相同的实例");
        }else{
            System.out.println("obj1 与 obj2 是不同的实例");
        }
    }
}

运行结果如下:

生成了一个实例
obj1 与 obj2 是相同的实例

Singleton 模式中的角色

Singleton

Singleton模式中只有Singleton这一个角色;Singleton角色中有一个返回实例的static方法,该方法总是会返回同一个实例。

UML

对Singleton模式的思考

单例模式的作用

单例模式的作用其一是为了解决多线程并发访问的问题。
例如:一个系统中可以存在多个预执行任务,但是只能有一个正在工作的任务。例如示例程序:

public class TicketMaker {
    private int ticket = 1000;
    private static TicketMaker ticketMaker = new TicketMaker();
    private TicketMaker(){

    }
    public static TicketMaker getInstance(){
        return ticketMaker;
    }

    public synchronized int getNextTicketNumber(){
        return ticket++;
    }
}

上述程序中,我们只希望生成一个TicketMaker实例,并且能够让getNextTicketNumber在多线程的环境下正常工作,所以我们需要将其定义为synchronized方法。

单例模式的作用其二是为了节约系统的内存,提高系统运行的效率,如果对于一种工具类,你需要频繁调用,如果每次调用都要产生新的实例,那么就会在浪费内存的空间,所以单例模式的一个优点就是减少资源消耗,并且能提高程序的运行速度。

一个问题

有单例模式如下:

public class Singleton1 {
    private static Singleton1 singleton1 = null;
    private Singleton1(){
        
    }
    public static Singleton1 getInstance(){
        if(singleton1 == null){
            singleton1 = new Singleton1();
        }
        return singleton1;
    }
}

为什么上述程序不是严格的Singleton模式 ?
答案:因为在多线程几乎同时调用Singleton.getInstance方法的时候,可能会生成多个实例。
首先将程序改动一下,降低一下处理速度:

public class Singleton1 {
    private static Singleton1 singleton1 = null;
    private Singleton1(){
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){

        }
    }
    public static Singleton1 getInstance(){
        if(singleton1 == null){
            singleton1 = new Singleton1();
        }
        return singleton1;
    }

}

在多线程的环境下,进行程序的测试:

public class Main extends Thread{
    public Main(String name){
        super(name);
    }

    public void run(){
        Singleton1 obj = Singleton1.getInstance();
        System.out.println(getName() + ": obj = " + obj);
    }

    public static void main(String[] args){
        new Main("A").start();
        new Main("B").start();
        new Main("C").start();
    }
}

程序运行的结果为:

C: obj = DesignMode.Singleton.Singleton1@5c13df07
B: obj = DesignMode.Singleton.Singleton1@50564e5e
A: obj = DesignMode.Singleton.Singleton1@5b1a5c4a

可以看到产生了多个不同的实例,为什么会出现这种情况呢?
原因在于,在多线程的环境下,使用如下代码

if(singleton == null){
    singleton = new Singleton();
}

进行判断是线程不安全的,如果在使用singleton == null 判断了第一个实例是否为空后,执行了语句,但是在赋值之前,其他的线程也在进行singleton == null的判断,继而创建出了不同的实例。那么如何修改本程序呢?
修改结果如下:

public synchronized static Singleton1 getInstance(){
        if(singleton1 == null){
            singleton1 = new Singleton1();
        }
        return singleton1;
    }

将getInstance变为 synchronized方法即可。对于这种单例模式:

public class Singleton1 {
    private static Singleton1 singleton1 = null;
    private Singleton1(){
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){

        }
    }
    public synchronized static Singleton1 getInstance(){
        if(singleton1 == null){
            singleton1 = new Singleton1();
        }
        return singleton1;
    }
}

我们称之为懒汉式单例模式,而最开始的那种单例模式则是饿汉式。两者的优缺点为饿汉式为线程安全,调用效率高,但是不能延时加载;懒汉式线程安全,但是因为 getInstance方法为synchronized,所以调用效率不高,但是可以延时加载。其实还有更好的实现单例模式的方法,比如:静态内部类实现单例模式:

public class Singleton2 {
    private static class SingletonClassInstance {
        private static final Singleton2 instance = new Singleton2();
    }

    private Singleton2() {
    }

    public static Singleton2 getInstance() {
        return SingletonClassInstance.instance;
    }
}

这种实现线程安全,调用效率高,并且可以做到延时加载。