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

所属分类: 软件编程 / java 阅读数: 29
收藏 0 赞 0 分享

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的面向对象编程基本概念学习笔记整理

这篇文章主要介绍了Java的面向对象编程基本概念学习笔记整理,包括类与方法以及多态等支持面向对象语言中的重要特点,需要的朋友可以参考下
收藏 0 赞 0 分享

Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

这篇文章主要介绍了Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法 的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

基于Java实现杨辉三角 LeetCode Pascal's Triangle

这篇文章主要介绍了基于Java实现杨辉三角 LeetCode Pascal's Triangle的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

Java中Spring获取bean方法小结

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中获取Spring配置的bean呢?下面通过本文给大家介绍Java中Spring获取bean方法小结,对spring获取bean方法相关知识感兴趣的朋友一起学习吧
收藏 0 赞 0 分享

如何计算Java对象占用了多少空间?

在Java中没有sizeof运算符,所以没办法知道一个对象到底占用了多大的空间,但是在分配对象的时候会有一些基本的规则,我们根据这些规则大致能判断出来对象大小,需要的朋友可以参考下
收藏 0 赞 0 分享

剖析Java中的事件处理与异常处理机制

这篇文章主要介绍了Java中的事件处理与异常处理机制,讲解Java是如何对事件或者异常作出响应以及定义异常的一些方法,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Java的Struts2框架的结构及其数据转移方式

这篇文章主要介绍了详解Java的Struts2框架的结构及其数据转移方式,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

Java封装好的mail包发送电子邮件的类

本文给大家分享了2个java封装好的mail包发送电子邮件的类,并附上使用方法,小伙伴们可以根据自己的需求自由选择。
收藏 0 赞 0 分享

在Java的Struts中判断是否调用AJAX及用拦截器对其优化

这篇文章主要介绍了在Java的Struts中判断是否调用AJAX及用拦截器对其优化的方法,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

java多线程Future和Callable类示例分享

JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。今天我们就来研究下Future和Callab
收藏 0 赞 0 分享
查看更多