单例的五种实现方式,及其性能分析

序言

在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

(*注意:由于不同电脑之间的性能差异,测试的结果可能不同)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值