首先什么是单例模式?为什么需要单例模式?
答:单例模式顾名思义就是在整个运行时,一个类只有一个实例对象
因为有的类的实例对象创建和销毁对资源来说消耗不大,但是有些类比较庞大和复杂如果频繁的创建和销毁对象,并且这些对象完全是可以复用的,会造成不必要的性能浪费
单例模式有多种写法需要考虑:
- 是否线程安全
- 是否是懒加载
- 能否能被反射破坏(这个不用考虑一般都是会被破坏的)
不会满足以上三点的单例
如下:首先来一个简单的单例
public class Singleton {
private Singleton(){} //构造器私有
private static Singleton instance = null; //初始化对象为null
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
上面代码如果其他类要使用singleton对象的话只能通过调用getInstance方法,进入方法后首先判断是否被构造过,如果是就直接使用,如果不是就new一个新的出来再使用。
如果要保证在同一时刻只有一个线程进入把代码改成如下代码:
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
但是现在这个代码有些问题比如:我们只想要在构建的时候同步线程,上面这个就是每次在获取的时候都要同步线程对性能影响很大。这种写法一般不可取。
上面我们发现线程安全出现在了构建对象的阶段,那么我们只要在编译期构建对象,在运行时调用,就不用考虑线程安全问题。代码如下:
public class Singleton {
private Singleton(){} //构造器私有
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
但是以上写法不是懒加载的。
我们现在看第二次写的代码,为何性能影响大,原因就在于在方法上定义了synchronized关键字,所以每次进入该方法的时候就必须首先去获取锁。
所以我们把代码改成如下:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getSingleton(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上代码运用了双检锁,双检锁是一种很常见的同步代码手段。
下面使用volatile就会阻止指令重排问题
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;
}
}
上面这种写法既满足了懒加载,又满足了线程安全问题,效率也比较高。
那还有没有更简洁的写法,如下:内部类的写法
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
这里,静态内部类在程序启动的过程中不会加载,只有在第一次被调用的时候才会被加载。这里主要是运用了jdk加载特性实现了懒加载。
但是上面我们说了会被反射破坏,但是可以利用枚举类型来不让反射破坏,但是枚举类型无法实现懒加载模式。
第二种饿汉式如下:
public class Singleton {
//饿汉式如下:
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
- 优点 :
1.线程安全
2.在类加载的同时已经创建好一个静态对象,调用时反应速度快 - 缺点 :
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化