单例模式的多种实现方式

时间:2022-05-22 17:23:27

       最近项目中为了系统中避免创建多个实例,teamleader让我把项目代码优化一下(应用单例模式),后面将单例模式的学习心得分享给团队同事,同事也给了我一些意见,现在写出来分享给大家,博友多多知道哈,写的不好给点意见哈!

现在从三方面讲解单例模式:

(1)单例模式概念
(2)单例模式特点
(3)常见的实现方式

单例模式概念:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。


单例模式特点:

1)单例类只能有一个实例
2)单例类必须自己创建自己的唯一实例
3)单例类必须给所有其他对象提供这一实例

常见的实现方式:从以下8中实现方式讲解

1)懒汉式,线程不安全
2)懒汉式,线程安全
3)恶汉式
4)恶汉式,变种
5)静态内部类
6)双重检验锁
7)登记式


1)懒汉式,线程不安全,代码如下:

public classSingleton {

  private static Singleton instance;

  private Singleton() {

  }

  public static Singleton getInstance(){

  if (instance == null) {

  instance = new Singleton();

 

  return instance;

  }

}


上述代码很简单,但是是线程不安全的,多线程情况下不能正常工作,会创建多个实例。为什么呢?

2) 懒汉式,线程安全,代码如下:
public classSingleton {
private staticSingletoninstance;
private Singleton(){
}
public staticsynchronized Singleton getInstance() {
if (instance== null) {#1
instance = newSingleton();#2
}
return instance;#3
}
}

上述代码为线程安全的,为什么呢?

当系统有两个线程线程1和线程2,两线程到达#1前,若线程1(或线程2)抢到锁,执行#2,#3后释放锁,若线程2(或线程1)抢到锁,会执行#2,由于instance为静态类变量,此时的instance已由线程1实例化了,因此线程2没有执行#2,因此系统只有一个实例化对象。所以该代码是线程安全的。缺点:尽管该代码是线程安全的,能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是效率低,99%情况下不需要加锁、解锁的同步操作。

3)恶汉式,代码如下:
public class Singleton { 
   private staticSingleton instance = new Singleton(); 
   private Singleton (){
   }
    public staticSingleton getInstance() { 
   return instance; 
  
}

上述代码在类一加载便实例化instance,因此称为恶汉式(很饥渴的意思,哈哈)


4)恶汉式,变种,代码如下:
public class Singleton {  
private Singleton instance = null;  
static {  
instance = new Singleton();  
}  
private Singleton (){}  
public static Singleton getInstance() {  
return this.instance;  
}  
}

上述代码是3)的变种,其实是一样的。

5)静态内部类,代码如下:
public class Singleton { 
 private static class SingletonHolder { 
 private static final Singleton INSTANCE = newSingleton(); 
 } 
 private Singleton (){}
 public static final Singleton getInstance(){ 
  return SingletonHolder.INSTANCE; 
 }
}

相对于第三种方式,这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显式通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance。例如,如果实例化instance很消耗资源,我想让其延迟加载,另一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。



6) 双重校验锁“双重检查加锁”的方式可以既实现线程安全,又能够使性能不受到很大的影响。


那么什么是”双重检查加锁“机制呢?

所谓双重检查加锁机制指的是:并不是每次进getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。


重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。(对于volatile修饰的变量,系统内的线程所有的write都将先行发生于read)

说明:由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。


现列出实例代码讲解:

public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton  getInstance() {  
if (singleton == null) {#2
synchronized (Singleton.class) {
if (singleton == null) {  
singleton = new Singleton();  
}  
}  
}  
return singleton;  
}  
}


注:很显然上述代码是线程安全的,然而细心的朋友可以发现,如果把去掉第一重判断#2,此时的代码也是线程安全的,与单例第二种实现方式没有本质的区别,那么为什么加上#2呢,好处有:提高代码运行效率,当线程1实例了singleton后,此时线程2判断singleton不为空,那么synchronized同步代码并需要执行,因为加锁、解锁很大的影响系统性能,尽量少用。


7)登记式,可继承
细心的朋友可以看出懒汉式或者恶汉式的单例模式并不能被继承,并没有体现面向对象的优势,看到网上有博友实现的登记式代码很长很繁琐,因此贴出相对简洁的可继承的登记式单例模式的代码是很有必要的,我也是才学习到,是腾讯大神分享给我的。
下面代码是Google公司自己写的代码,不愧是牛逼公司,代码简洁,代码如下:

public abstract class Singleton<T> {
private T mInstance;

    protected abstract T create();


    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}


由于静态类是需要继承才能实现起内部方法,是不是so easy!

如何继承呢?
public class AManager {

private static final Singleton<AManager> sInstance = new Singleton<AManager>() {


@Override
protected AManager create() {
return new AManager();
}
};

public static AManager getInstance(){
return sInstance.get();
}

private AManager(){
System.out.println("AManager created!");
}

}

上述代码很巧妙也很简单,就不用详细说明了哈,大家可以复制下来试试哈。


最后的最后,给出个总结:
      单例模式在内存中只有一个对象,节省内存空间;避免频繁的创建销毁对象,可以提高性能;避免对共享资源的多重占用;可以全局访问。使用时不能用反射模式创建单例,否则会实例化一个新的对象,使用懒单例模式时注意线程安全问题,恶单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(登记式)。最后大家根据项目需要使用七种的一种实现方式哈。