1.饿汉式
饿汉很饿,只想着尽早吃到。所以他就在最早的时机,即类加载时初始化单例,以后访问时直接返回即可。
// 饿汉// ThreadSafepublic class Singleton {private static final Singleton singleton = new Singleton();private Singleton() {}public static Singleton getInstance() {return singleton;}}
饿汉的好处是天生的线程安全(得益于类加载机制),写起来超级简单,使用时没有延迟;坏处是有可能造成资源浪费(如果类加载后就一直不使用单例的话)。
值得注意的时,单线程环境下,饿汉与饱汉在性能上没什么差别;但多线程环境下,由于饱汉需要加锁,饿汉的性能反而更优。
2.饱汉式
2.1基础饱汉
饱汉,即已经吃饱,不着急再吃,饿的时候再吃。所以他就先不初始化单例,等第一次使用的时候再初始化,即“懒加载”。
public class Singleton {
private static Singleton singleton = null;
private Singleton1() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2.2 DCL
public class Singleton{
private static volatile Singleton singleton = null; //volatile修饰
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
//当Thread1和Thread2,两个线程同时判断instance == null成立,此时,它们同时试图去获得锁
//由于synchronized的排它性,假设此时只有Thread1获得锁,Thread2被阻塞
//当Thread1执行完 new Singleton()操作之后,释放当前锁
//接下来,Thread2获得锁,此时Thread2会继续执行new Singleton()操作
//此时,Thread1和Thread2,就获得了不一样的instance实例
singleton = new Singleton();
}
}
}
return singleton;
}
}
volatile 修饰目的: 对象实际上创建对象要进过如下几个步骤:
- 分配内存空间。
- 调用构造器,初始化实例。
- 返回地址给引用
可能发生指令重排序的,那有可能构造函数在对象初始化完成前就赋值完成了,在内存里面开辟了一片存储区域后直接返回内存的引用,这个时候还没真正的初始化完对象。、、、 但是别的线程去判断instance!=null,直接拿去用了,其实这个对象是个半成品,那就有空指针异常了。
3.Holder
既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。
public class Singleton {
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
private SingletonHolder() {
}
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
