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

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

序言

在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.实例的初始化由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;

}

优点

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));

}

}

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

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饿汉式13
2懒汉式10778
3双重检查15
4静态内部类14
5枚举12

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

总结

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

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持! 

以上是 java 单例的五种实现方式及其性能分析 的全部内容, 来源链接: utcz.com/p/214015.html

回到顶部