单例的五种实现方式,及其性能分析
程序员文章站
2022-07-14 09:04:08
...
序言
在23种设计模式中,单例是最简单的设计模式,但是也是很常用的设计模式。从单例的五种实现方式中我们可以看到程序员对性能的不懈追求。下面我将分析单例的五种实现方式的优缺点,并对其在多线程环境下的性能进行测试。
实现
单例模式适用于资源占用较多的类,保证一个类只有一个实例即单例。通用的做法就是构造器私有化,提供一个全局的访问点,返回类的实例。
uml图:
1.饿汉式
/**
* 饿汉式
* @Description:
* 优点:1.实例的初始化由JVM装载类的时候进行,保证了线程的安全性
* 2.实现简单方便
* 3.实例的访问效率高
* 缺点:1.不能实现懒加载,如果不调用getInstance(),那么这个类就白白的占据内存,资源的利用率不高
* 注意:1.防止通过反射调用构造方法**单例模式。
* 2.防止通过反序列产生新的对象。
*/
public class SingleDemo {
private static SingleDemo instance = new SingleDemo();
//私有化构造器
private SingleDemo() {
//防止其他通过反射调用构造方法,**单例
if (instance != null) {
throw new RuntimeException();
}
}
//对外提供统一的访问点
public static SingleDemo getInstance() {
return instance;
}
}
2.懒汉式
/**
* 懒汉式实现单例
*
* @Description:
* 优点:只有使用这个类的时候才初始化实例,优化了资源利用率
* 缺点:为了实现线程安全,使用了同步方法获取,增加了访问的开销
* 注意:1.防止通过反射调用构造方法**单例模式。
* 2.防止通过反序列产生新的对象。
*/
public class SingleDemo2 {
// 此处并不初始化实例
private static SingleDemo2 instance;
private SingleDemo2() {
if (instance != null) {
throw new RuntimeException();
}
}
/**
* 当调用此方法的时候才初始化实例, 为了实现线程安全,需要使用同步方法
*
* @return
*/
public static synchronized SingleDemo2 getInstance() {
if (instance == null) {
instance = new SingleDemo2();
}
return instance;
}
}
3.双重检查
/**
* 双重检查
* @Description:
* 优点:实现懒加载,通过缩小同步区域和第一次检查提高访问效率
* 缺点:由于JVM内存模型和JVM指令重排序的原因,可能会出问题。不推荐使用。
* 注意:1.防止通过反射调用构造方法**单例模式。
* 2.防止通过反序列产生新的对象。
*
* 备注:instance = new SingleDemo3();可以分为以下3步完成(伪代码)
* memory = allocate(); //1.分配对象内存空间
* instance(memory); //2.初始化对象
* instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null
* 由于步骤2和步骤3间可能会重排序,如下:
* memory = allocate(); //1.分配对象内存空间
* instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
* instance(memory); //2.初始化对象
* 由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
* 但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。
* 所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
* 那么该如何解决呢,很简单,我们使用volatile禁止instance变量被执行指令重排优化即可。
* private volatile static DoubleCheckLock instance; //禁止指令重排优化
*
*/
public class SingleDemo3 {
private static SingleDemo3 instance;
private SingleDemo3() {
if (instance != null) {
throw new RuntimeException();
}
}
public static SingleDemo3 getInstance() {
//第一重检查,提高效率
if (instance == null) {
synchronized (SingleDemo3.class) {
//第二重检查保证线程安全
if (instance == null) {
instance = new SingleDemo3();
}
}
}
return instance;
}
}
4.静态内部类
/**
* 静态内部类实现单例
* @Description:
* 优点:即实现了线程安全,又实现了懒加载
* 缺点:实现稍显复杂
* 注意:1.防止通过反射调用构造方法**单例模式。
* 2.防止通过反序列产生新的对象。
*/
public class SingleDemo4 {
private static SingleDemo4 instance;
private static class SingleDemo4Holder {
private static final SingleDemo4 instance = new SingleDemo4();
}
private SingleDemo4() {
if (instance != null) {
throw new RuntimeException();
}
}
/**
* 调用这个方法的时候,JVM才加载静态内部类,才初始化静态内部类的类变量。由于由JVM初始化,保证了线程安全性,
* 同时又实现了懒加载
* @return
*/
public static SingleDemo4 getInstance() {
return SingleDemo4Holder.instance;
}
}
5.枚举实现
/**
* 枚举实现单例
* 枚举由JVM实现其的单例性
* @Description:
* 优点:1.实现简单
* 2.线程安全
* 3.天然对反射和反序列化漏洞免疫(由JVM提供)
* 缺点:不能实现懒加载
* 注意:1.防止通过反射调用构造方法**单例模式。
* 2.防止通过反序列产生新的对象。
*
*/
public enum SingleDemo5 {
INSTANCE;
}
测试代码
public class APP {
public static void main(String[] args) {
int threadCount = 100;
long start = System.currentTimeMillis();
final CountLock lock = new CountLock(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
//通过更换此处,来测试不同单例实现方式在多线程环境下的性能
SingleDemo5 demo = SingleDemo5.INSTANCE;
}
lock.finish();
}
}).start();
}
//等待所有线程执行完
lock.waitForWrok();
long end = System.currentTimeMillis();
System.out.println("总共耗时" + (end - start));
}
}
public class CountLock {
//线程的总数量
private int count;
public CountLock(int count) {
this.count = count;
}
/**
* 当一个线程完成任务以后,调用一次这个方法
*/
public synchronized void finish() {
count--;
if (count == 0) {
notifyAll();
}
}
/**
* 需要等待其他线程执行完的线程,调用此方法。
*/
public synchronized void waitForWrok() {
while (count > 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
五种单例实现方式,在100个线程下,每个线程访问1千万次实例的用时.
Tables | 实现方式 | 用时(毫秒) |
---|---|---|
1 | 饿汉式 | 13 |
2 | 懒汉式 | 10778 |
3 | 双重检查 | 15 |
4 | 静态内部类 | 14 |
5 | 枚举 | 12 |
(*注意:由于不同电脑之间的性能差异,测试的结果可能不同)
上一篇: opencv 第二课 矩阵的掩膜操作
下一篇: Java设计模式之简单工厂模式