单例模式
单例模式是创建型模式,它确保了一个类只能有一个对象被创建。
在单例模式中,需要注意以下几点:
- 构造方法必须为private,不能被其他类调用new方法
- 成员变量为static的,这样子才能被static方法返回
饿汉式
饿汉式在类加载的时候就被初始化,免去检查是否为空的步骤,但是如果一直没有被调用的话,就会造成浪费。
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
懒汉式
如下为懒汉单例模式的一个实例,他会在需要调用的时候才实行new初始化,为延迟加载的一个方法。但是这个方法是线程不安全的,如果有大量线程在初始化之前同时调用getInstance(),会导致new方法被调用多次,产生大量重复对象。
//懒汉单例模式
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(lazySingleton==null){
lazySingleton= new LazySingleton();
}
return lazySingleton;
}
}
Synchronized
为了让单例模式可以实现线程安全,我们可以利用synchronized修饰getInstance()方法。其中getInstance1()和getInstance2()效果是一样的,因为synchronized 修饰的是static方法,所以它也会锁住这个类
//懒汉单例模式
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton(){
}
public static synchronized LazySingleton getInstance1(){
if(lazySingleton==null){
lazySingleton= new LazySingleton();
}
return lazySingleton;
}
public static LazySingleton getInstance2(){
synchronized (LazySingleton.class){
if(lazySingleton==null){
lazySingleton= new LazySingleton();
}
}
return lazySingleton;
}
}
DoubleCheck
但是使用synchronized的效率并不高,我们可以利用双重检查double check的方法来创建单例模式
public class LazySingletonDoubleCheck {
private volatile static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck(){
}
public static LazySingletonDoubleCheck getInstance(){
if(lazySingletonDoubleCheck==null){
synchronized (LazySingletonDoubleCheck.class){
if(lazySingletonDoubleCheck==null){
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
//1.为对象分配内存
//2.调用构造方法初始化
//3.将对象的指针指向内存
}
}
}
return lazySingletonDoubleCheck;
}
}
因为之前的synchronized修饰的是getInstance()方法,即使对象已经初始化完成来,仍然需要上锁,效率很低。所以DoubleCheck把synchronized锁放到了判断对象为空之后。
这种需要重点注意的是,在lazySingletonDoubleCheck = new LazySingletonDoubleCheck();这一句中,涉及三个步骤:
第一步,为对象分配内存
第二步,调用构造方法初始化
第三步,将对象的指针指向内存
因为编译器的优化,其中第二步和第三步可能会重排序,导致顺序变成
1.为对象分配内存
2.将对象的指针指向内存
3.调用构造方法初始化
所以可能会出现以下情况:
所以我们需要给对象加上volatile关键词,让这个对象禁止重排序
静态内部类
可以使用静态内部类的方法构造,静态内部类相当于一个静态属性,只有在第一次加载类时才会初始化,在类初始化时,别的线程是无法进入的,因此保证了线程安全。
public class LazySingletonStaticInnerClass {
private LazySingletonStaticInnerClass(){
}
private static class InnerClassHolder{
private static LazySingletonStaticInnerClass lazySingletonStaticInnerClass = new LazySingletonStaticInnerClass();
}
public static LazySingletonStaticInnerClass getInstance(){
return InnerClassHolder.lazySingletonStaticInnerClass;
}
}