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

来源:互联网 发布:labview串口数据采集 编辑:程序博客网 时间:2024/05/16 12:23

序言

在23种设计模式中,单例是最简单的设计模式,但是也是很常用的设计模式。从单例的五种实现方式中我们可以看到程序员对性能的不懈追求。下面我将分析单例的五种实现方式的优缺点,并对其在多线程环境下的性能进行测试。

实现

单例模式适用于资源占用较多的类,保证一个类只有一个实例即单例。通用的做法就是构造器私有化,提供一个全局的访问点,返回类的实例。

uml图:
这里写图片描述

1.饿汉式

代码实现:

package com.zgh.gof23.singleton;/** * 饿汉式 * @author yuelin * */public class SingleDemo {    private static SingleDemo instance = new SingleDemo();    //私有化构造器    private SingleDemo() {        //防止其他通过反射调用构造方法,破解单例        if (instance != null) {            throw new RuntimeException();        }    }    //对外提供统一的访问点    public static SingleDemo getInstance() {        return instance;    }}
  • 1

优点

1.实例的初始化由JVM装载类的时候进行,保证了线程的安全性2.实现简单方便3.实例的访问效率高

缺点

1.不能实现懒加载,如果不调用getInstance(),那么这个类就白白的占据内存,资源的利用率不高

注意

1.防止通过反射调用构造方法破解单例模式。2.防止通过反序列产生新的对象。

2.懒汉式

代码实现:

package com.zgh.gof23.singleton;/** * 懒汉式实现单例 *  * @author zhuguohui * */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;    }}

优点

1.只有使用这个类的时候才初始化实例,优化了资源利用率

缺点

1.为了实现线程安全,使用了同步方法获取,增加了访问的开销

注意

1.防止通过反射调用构造方法破解单例模式。2.防止通过反序列产生新的对象。

3.双重检查

代码实现:

package com.zgh.gof23.singleton;/** * 双重检查 *  * @author zhuguohui * */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;    }}

优点

1.实现懒加载2.通过缩小同步区域和第一次检查提高访问效率

缺点

1.为了实现线程安全,使用了同步方法获取,增加了访问的开销

注意

1.防止通过反射调用构造方法破解单例模式。2.防止通过反序列产生新的对象。

4.静态内部类

代码实现:

/** * 静态内部类实现单例 *  * @author zhuguohui * */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;    }}

优点

1.即实现了线程安全,又实现了懒加载

缺点

2.实现稍显复杂

5.枚举实现

代码实现:

/** * 枚举实现单例 * 枚举由JVM实现其的单例性 * @author zhuguohui * */public enum SingleDemo5 {     INSTANCE;
  • 9

优点

1.实现简单2.线程安全3.天热对反射和反序列化漏洞免疫(由JVM提供)

缺点

2.不能实现懒加载

注意

1.防止通过反射调用构造方法破解单例模式。2.防止通过反序列产生新的对象。

测试

源码

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));    }}
  • 1
  • 2
  • 3

为了统计所以线程执行完需要的时间,我写了一个工具类

package com.zgh.gof23.singleton;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饿汉式132懒汉式107783双重检查154静态内部类145枚举12

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

总结

如果需要懒加载就使用静态内部类方式,如果不需要就使用枚举方式。