共计 3388 个字符,预计需要花费 9 分钟才能阅读完成。
什么是单例模式
单例模式属于创立型模式,它提供了一种创建对象的形式,确保只有单个对象被创立。这个设计模式次要目标是想在整个零碎中只能呈现类的一个实例,即 一个类只有一个对象。
比拟官网的一段话,能够说解释的很分明了。
解决了什么问题
因为在单例模式中,一个类只有一个对象,从其个性咱们能够很天然的想到单例模式能够
节约空间,节约工夫。
- 对于频繁应用的对象,尤其是比较复杂的对象创立,节俭了创建对象的工夫。
- 因为不须要频繁创建对象,咱们的 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 上新建单例类应用的是第一种饿汉式办法。
欢送拜访集体博客 获取更多常识分享。