单例模式应该算是我们经常遇到的一种模式,比如说线程池就是应用了单例,Spring bean也是运用了单例,单例模式节省了整个系统内存的开销。下面代码基于线程安全的前提下。
1、饿汉式。类加载时就生成了相应的对象。
public class Singleton1 {
private static Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}
2、懒汉式。在第一次调用方法时才生成对象。
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
3、双重检查式。两次判断,将同步放到方法内部,但是由于java内存模型,这个可能会出现问题。
public class Singleton3 {
private static Singleton3 instance;
private Singleton3(){}
public static Singleton3 getInstance(){
if(instance == null){
synchronized (Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
4、静态内部类。延迟加载,天然的线程安全。
public class Singleton4 {
private static final class InstanceClass{
private static Singleton4 instance = new Singleton4();
}
private Singleton4(){
if(InstanceClass.instance != null){
throw new RuntimeException();
}
}
public static Singleton4 getInstance(){
return InstanceClass.instance;
}
}
5、枚举式。jvm保证其线程安全性,效率也高。
public enum Singleton5 {
INSTANCE;
public void getInstance(){}
}
上面一共是五种单例的形式,每一种都有其对应的特点。总的来说,如果想要延迟加载,最好选用静态内部类,如果不需要,枚举较好。下面是对这五种模式创建对象效率的一个模拟测试,分别调用几种模式每次生成10万个对象。这里采用了countDownLatch类作为多线程环境下的计数器,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
public class SingleTest {
public static void main(String[] args) throws Exception {
Long startTime = System.currentTimeMillis();
int ThreadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(ThreadNum);
for (int i = 0; i < ThreadNum; i++) {
new Thread(() -> {
for (int j = 0; j < 1000000; j++) {
Singleton1 s = Singleton1.getInstance();
//Singleton5 s = Singleton5.INSTANCE;
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
Long endTime =System.currentTimeMillis();
System.out.println("耗时为:" + (endTime - startTime));
}
}
最后运行发现,除了懒汉式生成效率较低之外,其他几种模式差距都很小。