这里以两种解决DCL(双重检查锁)失效问题的方式来创建
1.利用volatile关键字解决DCL失效问题
public class VolatileSolveDCLSingleTon {
// volatile解决了处理器乱序执行的问题从而解决了DCL失效问题
private volatile static VolatileSolveDCLSingleTon instance;
private VolatileSolveDCLSingleTon(){}
public static VolatileSolveDCLSingleTon getInstance(){
// 为空时才去获取对象锁
if(instance == null){
synchronized (VolatileSolveDCLSingleTon.class){
// 此时仍为空才执行new操作,避免多个线程突破了第一层检查
if(instance == null){
instance = new VolatileSolveDCLSingleTon();
}
}
}
return instance;
}
}
2.使用静态内部类方式解决DCL失效问题
public class StaticSolveDCLSingleTon {
// 静态内部类方式解决DCL失效问题
private static class SingleTonHolder{
private static final StaticSolveDCLSingleTon instance = new StaticSolveDCLSingleTon();
}
private StaticSolveDCLSingleTon(){}
public static final StaticSolveDCLSingleTon getInstnce(){
return SingleTonHolder.instance;
}
}
3.可能有人想问什么是DCL失效问题,这里简单讲下:
DCL失效:
我们看instance = new SingleTon();这一句,在JDK1.5以前,处理器为了保证执行效率,会在保证程序执行结果不变的情况下进行指令的乱序执行,这一句处理器的执行过程分三步:
1.在堆中为SingleTon对象开辟一块存储空间
2.调用new SingleTon()来执行类的初始化
3.执行"="过程,即将instance引用指向SingleTon对象在堆中的内存空间
乱序执行发生在上面2、3的过程,也就是3可能会先于2执行,一般情况下这样是不影响程序的结果的,最终都是instance指向了对象在堆中的内存空间.
但是在DCL中,我们进行了两次instance判空,当第一次判空失败时我们会直接返回instance对象,问题就出在这里,当我们一个线程在执行上面new过程并且3先于2执行了的时候,假如有另一个线程也调用了getInstance方法,这时会进行第一次判空,由于3过程("="操作)执行后此时 instance != null,所以另一个线程会直接被返回instance,但是此时instance是未创建完全的,是不完整的,所以使用的时候就会出错,这就是DCL失效问题
纯手打,有错望指教
转载自如何优雅地手写单例模式
单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。正是因为简单,也成为面试中的众矢之的。本文来手写单例模式。
单例模式是一种常用的设计模式,该模式提供了一种创建对象的方法,确保在程序中一个类最多只有一个实例。
有一些对象其实我们只需要一个,比如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象,充当打印机、显示等设备的驱动程序对象。其实,这类对象只能有一个实例,如果制造出来多个实例,就会导致许多问题,如:程序的行为异常、资源使用过量,或者是不一致的结果。
Singleton通常用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。
在Java中实现单例模式,需要一个静态变量、一个静态方法和私有的构造器。
对于一个简单的单例模式,可以这样实现:
定义一个私有的静态变量uniqueInstance;
定义私有的构造方法。这样别处的代码无法通过调用该类的构造函数来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例;
提供一个getInstance()方法,该方法中判断是否已经存在该类的实例,如果存在直接返回,不存在则新建一个再返回。代码如下:
public class Singleton{ private static Singleton uniqueInstance;//私有静态变量 //私有的构造器。这样别处的代码无法通过调用该类的构造函数来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。 private Singleton(){} //静态方法 public static Singleton getInstance(){ //如果不存在,利用私有构造器产生一个Singleton实例并赋值到uniqueInstance静态变量中。 //如果我们不需要这个实例,他就永远不会产生。这叫做“延迟实例化(懒加载)“ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } }
这段代码使用了延迟实例化,在单线程中没有任何问题。但是在多线程环境下,当有多个线程并行调用 getInstance(),都认为uniqueInstance为null的时候,就会调用uniqueInstance = new Singleton();,这样就会创建多个Singleton实例,无法保证单例。
解决多线程环境下的线程安全问题,主要有以下几种写法:
关键字synchronized可以保证在他同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。
同步getInstance()方法是处理多线程最直接的做法。只要把getInstance()变成同步(synchronized)方法,就可以解决并发问题了。
public class Singleton{ private static Singleton uniqueInstance;//私有静态变量 //私有构造器 private Singleton() {} //synchronized同步方法 public static synchronized Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } }
但是,同步的效率低,会降低性能。只有第一次执行此方法的时候,才真正需要同步。也就是说,一旦设置好uniqueInstance变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘。同步getInstance()方法既简单又有效。如果说对性能要求不高,这样就可以满足要求。
之前的实现采用的是懒加载方式,也就是说,当真正用到的时候才会创建;如果没被使用到,就一直不会创建。
懒加载方式在第一次使用的时候, 需要进行初始化操作,可能会比较耗时。
如果确定一个对象一定会使用的话,可以采用“急切”地实例化,事先准备好这个对象,需要的时候直接使用就行了。这种方式也叫做饿汉模式。具体代码:
public class Singleton{ //在静态初始化器中创建单例,保证了线程安全性 private static Singleton uniqueInstance = new Singleton(); private Singleton() {} public static Singleton getInstance(){ return uniqueInstance; } }
饿汉模式是如何保证线程安全的?
饿汉模式中的静态变量是随着类加载时被初始化的。static关键字保证了该变量是类级别的,也就是说这个类被加载的时候被初始化一次。注意与对象级别和方法级别进行区分。
因为类的初始化是由类加载器完成的,这其实是利用了类加载器的线程安全机制。类加载器的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。
杀鸡用牛刀。实现单例模式可以利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样,只有第一次会同步。
public class Singleton{ //使用volatile关键字,确保当uniqueInstance变量被初始化成为Singleton实例时,多线程可以正确地处理uniqueInstance变量。 private volatile static Singleton uniqueInstance; private Singleton() {} public static Singleton getInstance() { if(uniqueInstance == null){//第一次检查 synchronized(Singleton.class){ if(uniqueInstance == null){//第二次检查 uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
如果性能是关注的重点,双重检查加锁可以大幅减少getInstance()的时间消耗成本。
在Java 1.5发行版本之前,双重检查模式的功能很不稳定,因为volatile修饰符的语义不够强,难以支持它。Java 1.5发行版本中引入的内存模式解决了这个问题,如今,双重检查模式是延迟初始化的一个实例域的方法。
从Java 1.5发行版本起,实现Singleton只需要编写一个包含单个元素的枚举类型:
public enum Singleton { INSTANCE; }
使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。注意:如果Singleton必须拓展一个超类,而不是扩展Enum的时候,则不宜使用这个方法。
package org.mlinge.s01; public class MySingleton{ private static MySingleton instance = new MySingleton(); private Mysingleton(){} public static MySingleton getInstance() return instance; }
package org.mlinge.s01; public class MyThread extends Thread{ }
- public class Singleton {
- /**
- * @author boker
- */
- private static Singleton instance;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- } else {
- return instance;
- }
- return instance;
- }
- }
3. 考虑到多线程问题,在2的基础上,对getInstance()方法加synchronized关键字 4. 考虑性能问题 synchronized关键字修饰getInstance()方法,会导致所有调用getInstance()方法的线程都要竞争同一把锁,即使在单例对象已经生成好的情况下。 这里使用double check的方式。另外,还需要注意instance在初始化过程中,可能已经不为NULL, 这样有可能会导致程序崩溃,因此这里加了个临时变量t,确保初始化完成之后再赋值给instance。public static Singleton getInstance() {
if (instance == null) { synchronized (Singleton.class) { if (instance == null){ Singleton t = new Singleton(); instance = t; } } } return instance; } 5.更简化的方法.基于静态内部类的特性,我们可以利用它的懒加载机制简化实现 public static Singleton getInstance(){
2 return NestedClass.instance; 3 } 4 5 private static class NestedClass{ 6 private static Singleton instance = new Singleton(); 7 }
} //懒汉式 public class Singleton {// 声明变量 private static Singleton singleton = null; // 私有构造函数 private Singleton() { } // 提供对外方法 public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } } return singleton; }
//1 public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){}; public Singleton getInstance(){ return instance; } } //2 public class Singleton{ private static class Holder{ private static Singleton instance = new Singleton(); } private Singleton(){}; public Singleton getInstance(){ return Holder.instance; } }