java单例模式
单例模式(Singleton Pattern)算是一个最简单的设计模式了,单例模式属于创建型模式,提供了一种创建对象的最佳方式。单例,顾名思义就是确保只有一个实例被创建。下面来说说我理解的一些思路。
通常我们创建实例对象都是通过new
来创建,但是每次调用new都会创建不同的对象。这在单例模式肯定是不允许的,所以第一步,我们要将无参的构造函数私有化,对外断了new的这条路,但是内部其实还是用new()来创建对象的,只是这个对象是私有的。那这样外面要如何来获得这个对象呢?类里面经常有private int age
这类私有的成员变量,我们通常都是用get和set方法来访问的,同样,单例模式也是通过方法来获得对象。好了,说了这么多,还是来看看具体的代码。
简单实现(饿汉式)
public class Singleton {
//创建私有化实例对象
private static final Singleton instance = new Singleton();
//构造函数私有化
private Singleton(){}
//通过方法来获得实例对象
public static Singleton getInstance(){
return instance;
}
}
这么一看单例模式代码还真少,其实也就三行(注释的部分),当然还有一点前面忘了说,就是方法要声明成static,不然外面根本没办法调用这个方法,因为无法用new来创建对象。既然方法是静态方法,那成员变量也要加上static,声明成类变量。一个简单的单例模式就是这样了,真的是太简单了,当初我临时抱佛脚,看了几遍代码就觉得掌握了,结果过了一段时间这么简单的都记不得了,现在好好理解一下想忘掉估计都很难。
其实这个就是饿汉式单例模式,并且是线程安全的,这种线程安全没有用到锁的机制,执行效率高,推荐使用(所以这个就是一个比较好的答案了,赶时间的同学下面可以不用看了)。但是有个缺点,实例instance并不是第一次调用属性Singleton.getInstance的时候才调用,而是第一次用到Singleton这个类的时候就会初始化instance。比如这个类里面有其他的静态方法,调用这个静态方法的时候instance也会被初始化,这样就浪费了内存,我现在还不需要你,你创建出来干嘛?这点可以向下面的懒加载学习。
懒汉式
来说说懒汉式,你可能听过lazy-load懒加载,就是当这东西要用到的时候再去加载,懒汉式差不多就是这样(这个例子不是很恰当,但是可以这么理解)。和前面的代码相比就是修改一下而已。
public class Singleton {
//这里没有 = new Singleton(),也就是没有创建对象
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//对象如果已经创建过了就直接返回这个对象
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}
这个就是懒汉式的代码,可以和前面的饿汉式对照一下。懒汉式不是线程安全的,这是懒汉式的致命缺陷,想要有懒汉式和饿汉式的优点?看看下面这个。
双检锁/双重校验锁
(DCL,即 double-checked locking),就是在前面懒汉基础上再加上一个null检查。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
首先看到有两个if判断,有人可能会想只要一个就好了,为什么需要两个呢?第一个if很好理解,如果已经创建了实例了,就不再创建了,而且第一个if可以避免进入到加锁的操作。为什么需要第二个if呢?试想一下,在多线程的情况下,有两个线程同时通过了第一个if的判断,这时一个线程会执行加锁的部分,而另一个线程则等待锁释放。如果没有第二个if,第一个线程创建实例后释放锁,第二个线程则会继续创建实例,这样就出现了两个实例了,所以这里需要有两个if来确保不会发生问题。
注意和前面不同的是我们用到了volatile关键字,volatile能够提供可见性,并且禁止指令重排序。因为new Singleton()创建对象不具有原子性,实例化对象大体分成三步
- 分配对象的内存空间
- 初始化对象
- 将变量指向分配的内存空间
由于存在指令重排列,可能先执行1,3,再执行2,当执行1,3之后,instance就不等于null了,所以其他线程在第一个if判断的时候就会将instance返回,但是这个实例还没有初始化,是不完整的实例,于是出现了错误。所以我们需要使用volatile关键字。
这种方法用到了锁的机制,锁比较耗时,所以相比较之下还是推荐使用饿汉式或者接下来提到的其他方法。
静态内部类
按需创建实例,线程安全。弥补前面饿汉式的缺点,当Singleton被装载了,instance也不一定会实例化。因为SingletonHolder这个静态内部类没有主动使用。并且没有用到锁,效率高。
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
用枚举的方式实现。实现单例模式的最佳方法。它更简洁,线程安全,自动支持序列化机制,止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Singleton6 {
INSTANCE;
public void whateverMethod() {
}
}