聊聊单例模式

介绍

单例模式(Singleton Pattern)是一种非常常用和非常简单的一种设计模式。一个类只允许创建一个实例,这种类就是一个单例类。

单例模式,为什么要使用它?使用它有什么好处?

在没有使用单例模式的时候,我们需要实例化各种对象,但是某些对象,它需要给全局使用,如果我们在使用的地方都去实例化它。那就达不到这个要求。

例如线程池,我全局只是使用一个线程池,那么只是需要创建一个线程池就好了。

还有,使用单例在某些情况下,能节省一些内存的消耗,毕竟它全局只有一个实例化的对象。

那么,现在就来看一个经典的单例对象。

单例对象

public class Singleton {
    private static Singleton singleton = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

这就是一个经典的单例对象,想要获取这个对象,必须要使用 getInstance() 方法来获取它。

public static void main(String[] args) {
    Singleton singleton1 = Singleton.getInstance();
    Singleton singleton2 = Singleton.getInstance();
    // true
    System.out.println(singleton1.equals(singleton2));
}

但是这种单例模式,只适合在单线程的情况去获取这个单例对象。

但是在多线程的情况下,就有可能会出现,创建了多个不同的对象实例。

所以,一般情况下都会给这个单例加上一把锁,在同一时间下只有一个线程能够对它进行创建和获取。

public static synchronized Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

是的,这样就可以解决多线程带来的问题了。但是,请注意这个单例在创建时是只有一个线程能去创建,那访问时也是这样,这样的性能就不会很乐观。其他的线程都在排队等待前面的线程获取完。

但,完全没有必要这样,因为已经实例化过了,就算再多的线程去获取这个对象也不会影响。

所以,这里还要继续优化。

双重检测单例

利用双重检测(double-checked locking),首先检查是否已经创建了实例,如果没有就创建(这里同时只能有一个线程去创建),已经创建就直接返回实例。

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }

        return singleton;
    }
}

这里需要注意的是 volatile 关键字,它能保证我们的代码编译时不会被打乱,重排序。但它并不能保证原子性,只能保证这个变量在线程的工作内存中的可见性和代码的有序性。

这样,就能保证获取单例对象时的性能了。

枚举单例模式

最后再说一种,枚举单例模式,枚举是线程安全的,所以,不必担心它在初始化时线程安全性的问题。

public enum IdGenerator {
    INSTANCE;

    private final AtomicInteger atomicInteger;

    IdGenerator() {
        this.atomicInteger = new AtomicInteger(0);
    }

    public Integer nextId() {
        return atomicInteger.incrementAndGet();
    }
}

好了,这就是枚举单例模式。实现起来也非常简单。

而且,在工作中也是非常常用的一种设计模式,应用场景也非常多。至于你想要怎么使用,就看你项目根据业务来结合吧。

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章