首页 > 试题广场 > 用至少两种方式实现一个Singleton(单例模式)。
[问答题]
用至少两种方式实现一个Singleton(单例模式)。
推荐
一。静态内部类实现:这种方式是 Singleton 类被装载了, instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  
二。双重检查锁:在JDK1.5之后,双重检查锁定才能够正常达到单例效果。(因为volitile关键字)
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

编辑于 2017-05-22 14:55:07 回复(0)

转载自如何优雅地手写单例模式

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。正是因为简单,也成为面试中的众矢之的。本文来手写单例模式。

单例模式是一种常用的设计模式,该模式提供了一种创建对象的方法,确保在程序中一个类最多只有一个实例。

有一些对象其实我们只需要一个,比如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象,充当打印机、显示等设备的驱动程序对象。其实,这类对象只能有一个实例,如果制造出来多个实例,就会导致许多问题,如:程序的行为异常、资源使用过量,或者是不一致的结果。

Singleton通常用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。

在Java中实现单例模式,需要一个静态变量、一个静态方法和私有的构造器。

经典的单例模式实现

对于一个简单的单例模式,可以这样实现:

  1. 定义一个私有的静态变量uniqueInstance;

  2. 定义私有的构造方法。这样别处的代码无法通过调用该类的构造函数来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例;

  3. 提供一个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实例,无法保证单例。

解决多线程环境下的线程安全问题,主要有以下几种写法:

同步getInstance()方法

关键字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的时候,则不宜使用这个方法。

发表于 2019-05-09 10:49:39 回复(0)

这里以两种解决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失效问题

纯手打,有错望指教

发表于 2017-09-08 17:34:53 回复(0)
1.线程安全版:饿汉式单例
饿汉式单例是指在方法调用前,实例就已经创建好了
package org.mlinge.s01;
public class MySingleton{
    private static MySingleton instance = new MySingleton();
    private Mysingleton(){}
    public static MySingleton getInstance() return instance;
}

2.懒汉模式
package org.mlinge.s01;
public class MyThread extends Thread{

}

发表于 2018-11-29 11:19:52 回复(0)
public class Sington{
 private Sington(){}
 private static class SingHolder{
  private static final Sington single = new  Sington();
 }
 public static Sington getInstance(){
  return SingHolder.single;
 }
}
public class SingleTon{
 private static SingleTon single;
 private SingleTon(){}
 public static SingleTon getInstance(){
  if (single == null){
   synchronized(SingleTon.class){
    if (single == null){
     single = new SingleTon();
    }
   }
  }
  return single;
 }
}
发表于 2018-04-20 09:25:45 回复(0)
1.利用枚举类型实现单例模式(单多线程都可以)
原理:当枚举类型只有一个成员变量时,就是最简单的单例模式实现方式
  1. public enum SingletonWithEnum {  
  2.     /** 
  3.      * @author boker 
  4.      */  
  5.     instance;  
  6.     public static SingletonWithEnum getInstance() {  
  7.           
  8.         return instance;  
  9.     }  
  10. }  
2.单线程实现(非线程安全,不适合多线程)
2.1 定义一个类,最好是final类
2.2 申明一个private的该类型的静态实例成员
2.3 实现一个private构造函数
2.4 一个public的静态方法getInstance()
	
  1. public class Singleton {  
  2.   
  3.     /** 
  4.      * @author boker 
  5.      */  
  6.     private static Singleton instance;  
  7.   
  8.     private Singleton() {  
  9.   
  10.     }  
  11.   
  12.     public static Singleton getInstance() {  
  13.         if (instance == null) {  
  14.             instance = new Singleton();  
  15.         } else {  
  16.             return instance;  
  17.         }  
  18.         return instance;  
  19.     }  
  20. }  
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 }

发表于 2017-09-06 20:49:18 回复(0)
//饿汉式
public class Singleton {
// 直接创建对象 private static Singleton instance = new Singleton(); // 私有化构造函数 private Singleton() { } // 返回对象实例 public static Singleton getInstance() { return instance; }
}


//懒汉式
public class Singleton {
// 声明变量 private static Singleton singleton = null; // 私有构造函数 private Singleton() { } // 提供对外方法 public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } } return singleton; }
发表于 2017-08-08 16:35:27 回复(0)
//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;
    }
}

发表于 2017-07-22 09:18:22 回复(0)