概述
单例模式指的是一个类保证只有这个类的一个实例的存在,比如网站的访问计数器一般就是单例模式,否则很难实现同步。
实现方式:
懒汉式:单例的实例在
第一次被使用时构建
饿汉式:单例的实例在类加载的时候构建
八种实现
class Singleton1 {
private static Singleton1 instance;
private Singleton1() {};
public static Singleton1 getInstance() {
if (instance == null)
return new Singleton1();
return instance;
}
}
懒汉式,这种方式线程不安全,很可能在一个线程正在执行实例的初始化并且还未完成的时候另一个线程判断instance为null也开始进行初始化操作。
class Singleton2 {
private static Singleton2 instance;
private Singleton2() {};
public static synchronized Singleton2 getInstance() {
if (instance == null)
return new Singleton2();
return instance;
}
}
这种方式使用了syncronized同步,在并发不多的情况下使用,但是如果并发获取单例对象很频繁的情况下由于是直接使用syncronized锁住了方法,从而导致效率很低下,虽然在JDK1.6之后synchronized的效率得到了很大的提升几乎和ReentrantLock差不多,但是明明第单例实例只需要在第一次使用的时候加载但是却每次使用的时候都伴随着被syncronized阻塞的开销似乎有点不太合理。
class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3() {}
public static Singleton3 getInstance() {
return instance;
}
}
这种方式基本没有问题,但是缺陷在于假设类中还有别的静态方法可以提供外部调用的时候,那么即使不是调用的getInstance方法instance还是会被构建(因为static的变量会在类初次被加载的时候初始化)。
class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4() {}
public static Singleton4 getInstance() {
return instance;
}
}
这种方式和第三种如出一辙
class Singleton5 {
private static class InstanceHolder {
private final static Singleton5 instance = new Singleton5();
}
private Singleton5() {};
public static Singleton5 getInstance() {
return InstanceHolder.instance;
}
}
比较推荐的一种方式,使用静态内部类实现,并且是懒汉式,实现延迟加载,因为只有在调用了getInstance方法的时候才会使用到InstanceHolder类,这个时候实例才会被构建。
class Singleton6 {
private static Singleton6 instance = null;
private Singleton6() {};
public static Singleton6 getInstance() {
if (instance == null){
synchronized(Singleton6.class) {
if (instance == null)
instance = new Singleton6();
}
}
return instance;
}
}
双重检查锁实现,乍一看基本没问题,而且效率上也得到了提升,因为第一次加载之后的调用都会直接返回return,只有在并发构建过程中才会被synchronized阻塞。
但是这种实现仍然是
线程不安全的,因为在instance=new Singleton();这里的操作实际上包含三步:
- 申请并分配对内存
- 实例化对象
- 将内存地址赋值给instance
这其中的2,3步骤在实际过程中可能会发生重排序(因为2,3步骤并没有直接的依赖关系),可能使得实际的顺序是1,3,2,这样就可能导致A线程在执行到1,3步骤的时候B线程同时判断instance为null(因为instance还没有被赋值),从而再一次的进行对象的构建操作。
class Singleton7 {
private static volatile Singleton7 instance = null;
private Singleton7() {};
public static Singleton7 getInstance() {
if (instance == null){
synchronized(Singleton6.class) {
if (instance == null)
instance = new Singleton7();
}
}
return instance;
}
}
线程安全。使用了volatile修饰instance,volatile的语义可以理解为用volatile修饰的变量的读/写操作不会被重排序,也就是说上面的1,2,3步相当于加了一个锁。
enum Singleton8 {
INSTANCE;
public void method() {
//。。。
}
}
最后一种使用枚举,线程安全。枚举型保证实例只会创建一次。