静态内部类
实现了线程安全,又避免了同步带来的性能影响。
public class Singleton{
private Singleton(){}
private static class LazyHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance(){
return LazyHolder.INSTANCE;
}
}
饿汉式单例
饿汉式单例类.在静态对象类初始化时,已经自行实例化,变量以后不再改变,所以是线程安全的,但是不是懒加载(延时加载)。缓存了很多实例类中、那么就得考虑效率问题,因为这种方式会把所有用不用的实例一起创建
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
public static Singleton1 getInstance() {
return single;
}
}
懒汉式单例
懒加载在需要的时候才去,所以是懒加载(延时加载),但是对象时不加上synchronized则会导致对对象的访问不是线程安全的。
public class Singleton{
private static Singleton singleton = null;
public static synchronized synchronized getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
不安全是由于当两个线程并发调用getInstaince时,会出现创建两个对象。
现在假设有两个线程,分别是线程1,线程2。
假设线程1开始执行,并且通过if判断执行到new Singleton()时,但是还没有完成创建对象,此时 singleton还是为null,这时被线程2抢占执行,也会进入if判断,由于两个线程都通过了if判断所以会创建两个对象。有这种情况发生所以是线程不安全。
懒汉式双重检查锁
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
安全过程:
线程 1 进入 getInstance() 方法。
由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 被线程 2 预占。
线程 2 进入 getInstance() 方法。
由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
线程 2 被线程 1 预占。
线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance。
线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
线程 1 被线程 2 预占。
线程 2 获取 //1 处的锁并检查 instance 是否为 null。
由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
破坏双重检查锁的过程:
主要突破点在懒汉式双重检查锁的//3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的。
线程 1 进入 getInstance() 方法。
由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
线程 1 被线程 2 预占。
线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
线程 2 被线程 1 预占。
线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
后续分析:http://www.iteblog.com/archives/773,作者分析的详细,专业,真心佩服。
结束语
为避免单例中代价高昂的同步,程序员非常聪明地发明了双重检查锁定习语。不幸的是,鉴于当前的内存模型的原因,该习语尚未得到广泛使用,就明显成为了一种不安全的编程结构。重定义脆弱的内存模型这一领域的工作正在进行中。尽管如此,即使是在新提议的内存模型中,双重检查锁定也是无效的。对此问题最佳的解决方案是接受同步或者使用一个 static field。