单例模式

在日常的开发过程中,我们需要使用到设计模式,而单例模式可谓是最常见的一种。正确的使用单例模式,不仅可以降低内存的使用率,也可以提升整个程序的运行效率。下面我来谈谈自己对单例模式的理解。


【1】懒汉式

特点:

(1)是一种牺牲时间换取空间的策略

(2)懒加载,只在需要的时候才实例化对象

public class Singleton {
private static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

}

然而这样的单例模式存在缺点:

(1)多线程情况下,容易被多个线程实例化出多个对象,违背”单例“的原则


【2】线程安全的懒汉式

考虑到在多线程的情况下,懒汉式存在问题,最普遍的想法,就是给getInstance()方法加上同步锁

public class Singleton {
private static Singleton instance;

private Singleton() {
}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

}

不过,由于对整个方法加锁,系统性能带来严重的降低,每次获取单例都需要先获取同步锁,而我们只需要在创建单例时加锁就行了。


【3】线程安全且性能较好的懒汉式(DCL)

public class SingletonDCL {
private volatile static SingletonDCL instance;

private SingletonDCL() {
}

public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}

}

这样的写法也称双重检验锁法(DCL),只在实例为null时才进行加锁。

双重判断使得当两个线程同时运行到加锁前的一步时,再顺序获得锁之后不会创建出两个实例来。

 

第二种方法和第三种方法的效率比较

public class Test {

public static void main(String args[]) throws InterruptedException {
long start_1 = System.currentTimeMillis();
List<Thread> list1 = new ArrayList<>();

//使用20000个线程去获取单例
for (int i = 0; i < 20000; i++) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Singleton.getInstance();
}
});
list1.add(thread1);
thread1.start();
}

//等待线程执行完毕
for (Thread thread : list1) {
thread.join();
}
long end_1 = System.currentTimeMillis();
System.out.println("非DCL方法的耗时:" + (end_1 - start_1));


long start_2 = System.currentTimeMillis();
List<Thread> list2 = new ArrayList<>();
for (int i = 0; i < 20000; i++) {
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
SingletonDCL.getInstance();
}
});
list2.add(thread2);
thread2.start();
}
for (Thread thread : list2) {
thread.join();
}
long end_2 = System.currentTimeMillis();
System.out.println("DCL方法的耗时:" + (end_2 - start_2));
}
}

输出为

【设计模式】单例模式_DCL

可以看得出使用双重检验锁之后,系统的性能带来了提高。


【4】饿汉式

特点:

(1)是一种牺牲空间换取时间的策略

(2)类加载时,就直接实例化对象

(3)使用类加载机制,避免了多线程的同步问题

public class Singleton {
private Singleton() {
}

private static Singleton instance = new Singleton();

public static Singleton getInstance() {
return instance;
}

}

这样的写法确实很简单,但存在以下的缺点

(1)无论当前类的实例什么时候用,都在类加载时一起实例化所有使用此模式创建的实例,大量实例存在于内存中,内存的使用率提高


【5】静态内部类

public class Singleton {
private Singleton() {
}

private static class SingletonHolder {
private static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}

}

这样做得好处是

(1)静态内部类不会在类加载时就创建此类的实例,只有在显示调用此静态内部类时,此静态内部类才会被加载,内部的对象实例才会被赋值,起到懒加载的作用。

(2)和饿汉式一样,使用类加载机制,避免多线程下的同步问题。


【6】枚举(“单例的最佳实践”)

public enum EnumSingleton {

INSTANCE;
Resource instance;

EnumSingleton() {
instance = new Resource();
}

public Resource getInstance() {
return instance;
}

}

Effective Java作者Josh Bloch 提倡的方式,Resource类代表需要应用单例模式的资源。在main方法中通过EnumSingleton.INSTANCE.getInstance()获得Resource的单例,
那么这个单例是如何被保证的呢? 
在枚举类中,构造方法是私有的,在我们访问枚举实例时会执行构造方法,同时每个枚举实例被public static final修饰且为EnumSingleton类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。 
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的Resource也被保证实例化一次。 


【7】登记式(不常用)

将整个项目中用到的单例作集中化管理,即使用容器进行管理。

public class SingletonManager {
private static Map<String, Object> map = new HashMap<>();

private SingletonManager() {
}

public static void addSingleton(String key, Object singleton) {
if (!map.containsKey(key)) {
map.put(key, singleton);
}
}

public static Object getSingleton(String key) {
return map.get(key);
}

}

这样写的好处是什么呢?

(1)提供统一的接口增加或获取实例,提高对单例的管理效率


碍于博主才疏学浅,对单例模式的讨论也就这么多了,我会在以后的学习还有接下来的工作中,不断地修改博客,尽量将错误减少到最少,将优质的博文呈现给大家。