一 . 懒汉模式(Lazy)
懒汉模式比较复杂,需要注意到底点很多,下面会详细介绍。懒汉模式简而言之就是用到了才去实例化对象,延迟加载。
代码如下:
class SingletonLazy {
private static volatile SingletonLazy singletonLazy = null;
private SingletonLazy(){}
public static SingletonLazy getInstance() {
if (singletonLazy == null) {
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
}
singletonLazy == null
以上代码需要注意以下问题:
1. 覆盖一个private的构造方法,只提供一个单一的接口。
2. 提供一个单一的静态方法getInstance()。
3. 在创建类的地方加锁,防止多线程是创建多个对象出来,因为有可能有两个线程同时进入判断singletonLazy== null的代码块中。
4.双重锁模式。两次判断是否为null,因为初始化只有一次,所以进入该方法的大概率事件是不为空的,如果没有第一次判断就让每次多线程进来都排队判断,减少处理效率。
5. 在静态变量singletonLazy 前加volatile。volatile关键字有两个作用 1)保证可见性,2)防止指令重排序。在这里是作用2)。具体解释如下:
singletonLazy = new SingletonLazy();
以上代码,在被jvm编译器后会变成三个步骤,
1). 在堆内存区域开辟一个空间。
2). 给以上空间赋初始值。
3). 将这个空间的地址赋值给singletonTest。
因为singletonLazy = new SingletonLazy();并不是原子操作,而是被jvm编译成了以上3步。在jvm和cpu多代码进行执行时可能会对编译后的代码进行优化,对于没有依赖的代码进行指令重排,1)和2),3)之间是有依赖关系的,但是2)和3)之间是没有依赖关系的。意味着,3)可能在2)之前执行。再多线程环境中,如果线程一种的3)先执行,线程二刚好执行到if singletonLazy == null, 只是会之间返回singletonTest还有没赋初始值的对象出去。
二, 饿汉模式.
饿汉模式比较简单,简单的说就是在类初始化的时候就已经创建好对象了, 后面需要访问的对象直接去取就行了。本质上借助JVM的类加载机制,保证实例的唯一性,因为初始化只会执行一次。
代码如下:
class SingletonHungry{
private static SingletonHungry singletonTest = new SingletonHungry();
private SingletonHungry(){}
public static SingletonHungry getInstance(){
return singletonTest;
}
}
需要注意,只有真正使用到类的时候才会导致类的初始化。以下情况会导致类的初始化:
1. 当前类是启动类,即main方法所在的类。
2. 直接对类进行new操作。
3. 访问类的静态属性,静态方法。
4. 使用反射访问类。
5. 初始化一个了的子类。
三, 内部类模式
内部类模式是目前最为推荐的一种单例模式。因为他即达到类懒加载的目的,有不用像写懒汉模式那么复杂。
class SingletonInnerClass{
private SingletonInnerClass(){}
public static SingletonInnerClass getInstance(){
return InnerClass.singleton;
}
private static class InnerClass{
private static final SingletonInnerClass singleton=new SingletonInnerClass ();
}
}
知识点:
1. 一个private的构造方法,防止直接对对象的new操作。
2. 提供一个静态的唯一访问接口。
3. 一个静态内部类,该内部类的静态变量,只有在内部类被访问时候才会被被初始化。即在外边内的静态变量被初始化的时候,内部内的静态变量是不是被初始化的。
4. 和饿汉模式一样,借助jvm初始化只有一次来保证单例。
四, 枚举模式
枚举模式的本质还是饿汉模式, 在初始化枚举类的时候对静态final成员变量赋值。枚举模式的唯一优势是写起来简单,但是可读性差。
public class SingleEnumTest {
public static void main(String [] args){
SingltonEnum enum1= SingltonEnum.INSTANCE;
SingltonEnum enum2= SingltonEnum.INSTANCE;
System.out.println(enum1==enum2);
}
}
enum SingltonEnum{
INSTANCE;
}