几种线程安全的单例模式

记下几种常见的单例模式,并且都是线程安全的。

第一种:静态工厂方法,类加载的时候就初始化。

public class SingletonFirst {

  private static SingletonFirst singletonFirst = new SingletonFirst();
  
  public static SingletonFirst getInstance() {
    return singletonFirst;
  }
  
  private SingletonFirst() {
    //private constructor
  }
}

代码如上所示,非常简洁,当类被加载到JVM的时候,singletonFirst的单例就会被初始化,这样有就有一个问题,当我们只有声明但没有使用类的时候,也会占用空间。

第二种:DCL(double checked locking) (注:Picasso就是使用的这种单例的初始化方式,只是lock修改为SingletonSecond.class即可)

public class SingletonSecond {
  
  private static SingletonSecond singletonSecond = null;
  private static final byte[] lock = new byte[0];
  
  private SingletonSecond() {
    
  }
  
  public static SingletonSecond getInstance() {
    if(singletonSecond == null) {
      synchronized (lock) {
        if(singletonSecond == null) {
          singletonSecond = new SingletonSecond();
        }
      }
    }
    return singletonSecond;
  }

}

这种是一个线程安全的Lazy Initialization的方法。如果我们没有调用getInstance()的时候,是不会实例化这个类的,但当调用getInstance()的时候,会首先检查实例是否生成,如果没有,那么则进入同步代码块,在此之前,多个线程可能会同时尝试进入该代码块,即在判断if(singletonSecond==null)的时候,多个线程可能同时访问并且同时条件成立。因此,当进入同步代码块的时候,需要再次判断singletonSecond是否被初始化过,如果没有,则生成新的实例。最后返回唯一的实例。

第三种:方法二的简化版本

public class SingletonThird {
  
  private static SingletonThird singletonThird = null;
  
  private SingletonThird() {
    
  }
  
  public static synchronized SingletonThird getInstance() {
    if(singletonThird==null) {
      singletonThird = new SingletonThird();
    }
    return singletonThird;
  }

}

该版本其实如第二种类似,但书写简单,在方法上做同步,效率基本一致,不会由太多的额外消耗。此代码参考与Volley的源代码。

第四种:使用静态内部类作为辅助的办法

public class SingletonFourth {
  
  private SingletonFourth() {
    
  }
  
  public static SingletonFourth getInstance() {
    return SingletonInitializeClass.INSTANCE;
  }
  
  private static class SingletonInitializeClass {
    public static final SingletonFourth INSTANCE = new SingletonFourth();    
  }
}

当调用getInstance()的时候,会访问子类,如果子类没有加载到JVM的时候,JVM会将其载入并实例化INSTANCE,此时是由JVM保证其线程安全。

以上几种在实际生产环境中都是可以直接使用的,基本上都差不多。

第五种:枚举类实现单例模式

public enum SingletonFifth {

  INSTANCE;
    
  public static SingletonFifth getInstance() {
    return INSTANCE;
  }
}

这种在序列化和反序列化的时候优点更加突出,并且可以避免用户使用反射等机制获取新的对象。

下面是测试的代码,主要的思路是同时开100个线程去尝试获取单例模式的实例,然后将其放入集合中,然后打印集合中的元素,如果集合中有且仅有1个元素的话,那么即可说明线程安全的,如果在集合中有多余1个的元素,那么说明多个线程中去尝试获取单例的实例的时候,却拿到了不同的实例,那么如果出现这种情况的话,就不是线程安全的。

相关代码如下:

1、Worker线程,用来在不同线程中获取单例模式的实例。

import java.util.concurrent.CountDownLatch;

public abstract class Worker implements Runnable {
  
  private CountDownLatch mLatchStart;
  private CountDownLatch mLatchStop;
  
  public Worker(CountDownLatch start, CountDownLatch stop) {
    this.mLatchStart = start;
    this.mLatchStop = stop;
  }

  @Override
  public void run() {
    // TODO Auto-generated method stub
   try {
     mLatchStart.await(); 
     doWork();
     mLatchStop.countDown();
   } catch (InterruptedException ex) {
     ex.printStackTrace();
   }
  }
  
  abstract void doWork();
  
}

如上所示,使用CountDownLatch来实现多个线程中的同时启动,等待所有的线程的完成。

主测试代码:

package Singleton;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

public class Test {
  
  public static void main(String[] args) {
    
    CountDownLatch mLatchStart = new CountDownLatch(1);
    CountDownLatch mLatchStop = new CountDownLatch(100);
    
    mLatchStart = new CountDownLatch(1);
    mLatchStop = new CountDownLatch(100);
    final Set<SingletonFirst> setFirst = new HashSet<>();
    for(int i=0; i<100; i++) {
      new Thread(new Worker(mLatchStart, mLatchStop) {
        
        @Override
        void doWork() {
          // TODO Auto-generated method stub
          setFirst.add(SingletonFirst.getInstance());
        }
      }).start();
    }
    mLatchStart.countDown();
    try {
      mLatchStop.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println(setFirst);
    
    mLatchStart = new CountDownLatch(1);
    mLatchStop = new CountDownLatch(100);
    final Set<SingletonSecond> setSecond = new HashSet<>();
    for(int i=0; i<100; i++) {
      new Thread(new Worker(mLatchStart, mLatchStop) {
        
        @Override
        void doWork() {
          // TODO Auto-generated method stub
          setSecond.add(SingletonSecond.getInstance());
        }
      }).start();
    }
    mLatchStart.countDown();
    try {
      mLatchStop.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println(setSecond);
   
    
    mLatchStart = new CountDownLatch(1);
    mLatchStop = new CountDownLatch(100);
    final Set<SingletonThird> setThird = new HashSet<>();
    for(int i=0; i<100; i++) {
      new Thread(new Worker(mLatchStart, mLatchStop) {
        
        @Override
        void doWork() {
          // TODO Auto-generated method stub
          setThird.add(SingletonThird.getInstance());
        }
      }).start();
    }
    mLatchStart.countDown();
    try {
      mLatchStop.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println(setThird);
    
    
    mLatchStart = new CountDownLatch(1);
    mLatchStop = new CountDownLatch(100);
    final Set<SingletonFourth> setFourth = new HashSet<>();
    for(int i=0; i<100; i++) {
      new Thread(new Worker(mLatchStart, mLatchStop) {
        
        @Override
        void doWork() {
          // TODO Auto-generated method stub
          setFourth.add(SingletonFourth.getInstance());
        }
      }).start();
    }
    mLatchStart.countDown();
    try {
      mLatchStop.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println(setFourth);
    
  }
}

运行结果:

SingletonFirst:[Singleton.SingletonFirst@3801167a]
SingletonSecond:[Singleton.SingletonSecond@72116e25]
SingletonThird:[Singleton.SingletonThird@44bb11cd]
SingletonFourth:[Singleton.SingletonFourth@59ca08c]

如上所示,所有的集合中都有唯一的元素,是线程安全的。

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注