什么是单例模式
单例模式属于创建型模式,它提供了一种创建对象的方式,确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
比较官方的一段话,可以说解释的很清楚了。
解决了什么问题
由于在单例模式中,一个类只有一个对象,从其特性我们可以很自然的想到单例模式可以
节约空间,节约时间。
- 对于频繁使用的对象,尤其是比较复杂的对象创建,节省了创建对象的时间。
- 因为不需要频繁创建对象,我们的GC压力也减轻了,而在GC中会有STW(stop the world),从这一方面也节约了GC的时间。
结合单列模式的特点,可以顺理成章的知道它的应用场景。
实现方式
那么我们怎么实现一个单例模式呢,我们来思考我们的目标---保证在一个应用中一个类只有一个对象。
于是,我们就有下边的步骤:
- 构造方法私有化
- 在本类中创建一个对象
- 定义一个public方法,在其他程序使用时提供这个已经创建好的对象
饿汉式
<font color='red'>【可以使用】</font>
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
- 优点
实现简单,由于在类加载时就完成了实例化对象,没有线程安全问题。 - 缺点
由于在类加载时就实例化对象,如果后面我们没有用到这个对象,就造成了对资源的浪费。当然,可以忽略不计。
饿汉式的;另一种写法:
<font color='red'>【可以使用】</font>
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
懒汉式
也称饱汉式,与饿汉式不同的是,类的实例在获得的方法中创建。
<font color='red'>【线程不安全,不可使用】</font>
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
显然,这样写并不保证线程安全。比如有这样两个线程A,B,它们都去调用 getInstance( )方法去拿这个类的对象。由于一开始没有做过实例化,instance为null,A线程在执行完if (null == instance)
后,这时线程B也执行到此处,那A线程还没有完成类的实例化,instance还是null,故两个线程都调用了new去实例化了两个对象。?
那到这里,就会很简单的想到加锁来解决线程安全的问题了。
<font color='red'>【效率低下,不推荐使用】</font>
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
观察上面的代码,我们可以看到,在每次调用getInstance() 时,即使该类已经实例化,还是要去做同步,而这种情况只要return就行了。
那思考到这里,顺理成章的能想到,既然这样锁在方法上面效率低下,那就缩小锁的范围,所在这个类上面。于是有下面的实现:
<font color='red'>【线程不安全,不可以使用】</font>
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
嗯,完美!但是仔细看一看,好像哪里不对啊。?在多线程场景下,我们再捋一下这块代码的执行流程。同样有这样两个线程A,B,它们都去调用 getInstance( )方法去拿这个类的对象。由于一开始没有做过实例化,instance为null,A线程在执行完if (null == instance)
后,拿到了锁。这时线程B也执行到此处,但是由于被锁了,就等待A线程锁的释放。A线程执行new实例化了Singleton,执行完毕后,释放了锁。那B又得到了锁,继续执行Singleton的实例化,悲剧!!!两个线程都调用了new去实例化了两个对象。?
经过一代又一代人的不懈努力,在此基础上,又有了下面的完美解决方式:
懒汉式双重校验锁
<font color='red'>【可以使用】</font>
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
继续上面捋代码的逻辑,等到A线程实例化完成后,释放了锁。这时B线程执行到if ( null == instance )
,由于A线程已经完成了实例化,故直接返回Instance。
这里需要注意的是volatile关键字,JVM会有指令重排的情况,会造成获取到的对象没有被实例化。使用该关键字,可以保证修饰的关键字前后执行顺序唯一,不会进行指令重排。
内部类
<font color='red'>【可以使用】</font>
public class Singleton {
private Singleton() {
}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
与饿汉式相似,内部类的实现方式都通过在类加载时实例化的方式保证线程安全。
不同点是饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用。但是静态内部类的实现方式在Singleton类被装载时并不会被立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。与饿汉式相比,这种实现方式既没有浪费资源,又能保证线程安全。
枚举
<font color='red'>【推荐使用】</font>
public enum SingletonEnum {
instance;
private SingletonEnum() {
}
public void method() {
}
}
class Aa {
public static void main(String[] args) {
SingletonEnum.instance.method();
}
}
在最后比较推荐使用枚举方式实现单例模式,IDEA上新建单例类使用的是第一种饿汉式方法。
欢迎访问个人博客 获取更多知识分享。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。