1.概述

  最近在优化公司以前老项目的代码时,发现有些类的代码频繁地创建和销毁对象,资源消耗比较严重。针对这些做了一些优化,改用单例模式,避免频繁的创建和销毁对象,说起单例模式,相信每个人都会写,接下来,我们来说下单例模式的优化。

2.优化

  单例模式,顾名思义就是只有一个实例,可以分为饿汉式和懒汉式;

  饿汉式:

public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
public class Singleton {
private static Singleton instance = null;
static{
instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}

  优缺点分析:上述两种方式其实本质都是一样的,都是利用static来定义静态成员变量或静态代码。

        该种单例模式比较简单,在类加载的时候就已经创建好了实例,利用Class的类加载机制实现了线程安全单例

        但是这种模式的缺点就是在类加载的时候就已经完成了实例化,没有达到lazy-loading的效果,如果该实例始终没有用到,那么就会造成内存的浪费。

  饿汉式优化*1:

  

public class Singleton {
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}

  优缺点分析:这种方式相比于前面两种有所优化,使用了lazy-loading。Singleton类被装载后并没有立即生成实例,当getInstance方法被调用时,才会装载SingletonHolder类,从而实例化instance。  

  单例优化:

public enum Singleton{
INSTANCE;
public void method(){
}
}

  优缺点分析:该方式是JDK1.5的时候加入的,不仅能避免多线程同步问题,还可以防止反序列化重新创建新的对象。

  上述几种方式,其实实现原理都是借助了JVM进行类加载的时候初始化单例,就是ClassLoader的线程安全机制。

  ClassLoader的线程安全机制就是指ClassLoader的loadClass方法在加载类的时候使用synchronized关键字。所以这就是为什么在类加载的过程中是线程安全的了。

  

  懒汉式:

public class Singleton(){
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
if(null == instance){
singleton = new Singleton();
}
return singleton;
}
}

  优缺点分析:这种方式实现了lazy-loading,但是有一个明显的缺点就是,这种单例只能在单线程环境下使用,在多线程环境下,一个线程刚刚通过(null == instance)语句的同时,另一个线程也通过了该语句块,那么这个时候就会产生两个实例,这样就与单例模式只有一个实例的核心思想相悖了。所以在多线程模式不可使用该种方式。

  懒汉式优化*1:

public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

  优缺点分析:这种方式相比上面那种方法实现了线程同步,但是这个时候有个非常明显的缺点,就是效率低下,当调用getInstance()方法的时候都要进行同步,其实优化方式很明显,如果没有该实例,只需要在创建该实例的代码上添加synchronized代码块即可,若该实例已经存在,直接return该实例即可。

  懒汉式优化*2:

public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}

  优缺点分析:由于上面的方法同步效率太低,改用同步代码块的方法,但是该种方式根本不能起到线程同步的作用,因为由于实例化对象时,内存对象会进行重排序,就有可能会导致多线程的时候执行到if判断的时候还没被初始化或者得到一个不是null但是还未初始化完成的对象。

  懒汉式优化*3:

public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

  优缺点分析:这种Double-Check-Lock的方式进行了两次实例是否为空的判断,因为volatile 关键字就指定了禁止重排序,volatile保证了变量修改的可见性,但不保证原子性。一个线程对变量的修改,另一个线程能立即读到这个修改后的值,volatile是遵循happens-before原则的,这样多线程环境下,进行if判断的时候,能得到一个完整的已经实例好的对象,别的线程进行if判断的时候,直接返回该对象即可。这样我们就可以实现线程安全了,并且该种方式也实现了lazy-loading,效率高。

  那么有没有一种方式可以在不使用lock、synchronized的方式下实现线程安全的单例模式呢?答案是,有的,那就是使用CAS。

  CAS是什么呢?Compare And Swap,顾名思义就是比较和交换。CAS是项乐观锁技术,其包含三个参数,分别为V(待更新的值)、E(期望值)、N(新值),当V和E不相同时,说明其他线程已经做过更新了,此时该线程不执行更新操作,或者再次尝试读取V值再次尝试修改该值,也可以选择放弃该操作。若是V和E相等,则当前线程可以修改V值,也就是执行CAS操作。CAS操作中没有锁的参与,但是针对其他线程针对共享资源的操作做了处理。由于CAS中没有锁的参与,所以针对线程共享资源的操作也不会发生死锁了,可以说CAS天生免疫死锁。

public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton(){}
public static Singleton getInstance(){
for(;;){//不限次数的自旋循环,如果CAS一直失败,CPU的执行开销被消耗很严重
Singleton singleton = INSTANCE.get();
if(null != singleton){
return singleton;
}
singleton = new Singleton();
if(INSTANCE.compareAndSet(null, singleton)){//当前实例为null,才替换当前实例为singleton
return singleton;
}
}
}
}

  优缺点分析:该方式使用CAS实现线程安全,实现相比传统的锁机制来说,CAS依靠的是底层硬件(CPU的CAS指令)来实现的,不需要进行频繁的线程切换和阻塞而造成资源的额外消耗。

        但是这种方式还是有缺点的,CAS的自旋循环如果长时间不成功,则会给CPU带来非常大的执行开销。另外一点就是如果N个线程同时执行到singleton=new Singleton()的时候,则会同时创建大量的实例,很有可能发生OOM。

  CAS的缺点:首先CAS的ABA问题,这个可以通过添加版本号或时间戳来解决,在比较完内存中的值以后,再比较时间戳或者版本号是否一致。

        CAS的自旋操作,如果CAS长期不成功,会一直重试,会严重增加CPU的执行开销。JDK1.6以后默认开启了自旋(--XX:+UseSpinning),可以通过JVM设置CAS的自旋操作次数来解决(-XX:PreBlockSpin=10,JVM的默认自旋次数是10),当超过指定次数后,自动失败退出。还有一种自适应自旋锁,自旋的时间不再固定,会根据前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定的。

        CAS的功能的局限性,CAS只能保证单个内存中的值的原子性,在java中原子性不一定能保证线程安全,还需要volatile保证有序性来实现线程安全。在需要保证多个内存中的值的情况下,CAS也无能为力,可以看情况使用悲观锁。所以说在并发冲突概率比较高的环境中,尽量不要使用CAS。其次CAS的核心是依靠可以直接调用底层资源的Unsafe类的CompareAndSwap()方法实现的,在java使用只能使用Atomic包下的相关类,局限性比较大。

  

  参考文档:https://www.cnblogs.com/kismetv/p/10787228.html

单例模式的优化之路(java)的更多相关文章

  1. 我的阿里之路+Java面经考点

    我的阿里之路+Java面经考点 时间:2018-03-19 23:03  来源:未知   作者:admin   点击:87次 我的2017是忙碌的一年,从年初备战实习春招,年三十都在死磕JDK源码,三 ...

  2. 阿里巴巴 web前端性能优化进阶路

    Web前端性能优化WPO,相信大多数前端同学都不会陌生,在各自所负责的站点页面中,也都会或多或少的有过一定的技术实践.可以说,这个领域并不缺乏成熟技术理论和技术牛人:例如Yahoo的web站点性能优化 ...

  3. JVM性能优化系列-(1) Java内存区域

    1. Java内存区域 1.1 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.主要包括:程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运 ...

  4. 微博MySQL优化之路--dockone微信群分享

    微博MySQL优化之路 数据库是所有架构中不可缺少的一环,一旦数据库出现性能问题,那对整个系统都回来带灾难性的后果.并且数据库一旦出现问题,由于数据库天生有状态(分主从)带数据(一般还不小),所以出问 ...

  5. 新浪微博iOS客户端架构与优化之路

    新浪微博iOS客户端架构与优化之路   随着Facebook.Twitter.微博的崛起,向UGC.PGC.OGC,自媒体提供平台的内 容消费型App逐渐形成了独特的客户端架构模式.与电商和通讯工具类 ...

  6. Linkedin工程师是如何优化他们的Java代码的(转)

    英文原文:LinkedIn Feed: Faster with Less JVM Garbage 最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客上面发现了一篇很不错博文.这篇博文介绍 ...

  7. Golang 优化之路——bitset

    写在前面 开发过程中会经常处理集合这种数据结构,简单点的处理方法都是使用内置的map实现.但是如果要应对大量数据,例如,存放大量电话号码,使用map占用内存大的问题就会凸显出来.内存占用高又会带来一些 ...

  8. Linkedin工程师是如何优化他们的Java代码的

    http://greenrobot.me/devpost/java-faster-less-jvm-garbage/ Linkedin工程师是如何优化他们的Java代码的 最近在刷各大公司的技术博客的 ...

  9. Java 性能优化手册 — 提高 Java 代码性能的各种技巧

    转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...

随机推荐

  1. linux 安装 PHP fileinfo 扩展

    将windows解压Linux服务器 1.错误: PHP Fileinfo extension must be installed/enabled to use Intervention Image. ...

  2. Doctype作用?标准模式与兼容模式各有什么区别?

    Doctype作用?标准模式与兼容模式各有什么区别? DOCTYPE是document type(文档类型)的简写,用来告诉浏览器的解析器使用哪种HTML或XHTML规范解析页面.DOCTYPE不存在 ...

  3. B20J_2836_魔法树_树链剖分+线段树

    B20J_2836_魔法树_树链剖分+线段树 题意: 果树共有N个节点,其中节点0是根节点,每个节点u的父亲记为fa[u].初始时,这个果树的每个节点上都没有果子(即0个果子). Add u v d ...

  4. BZOJ_2134_单选错位——期望DP

    BZOJ_2134_单选错位——期望DP 题意: 分析:设A为Ai ∈ [1,ai+1] 的概率,B为Ai = A(imodn+1)的概率显然P(A|B) = 1,那么根据贝叶斯定理P(B) = P( ...

  5. XMR挖矿教程

    XMR挖矿教程 XMR介绍 门罗币(Monero,代号XMR)是一个创建于2014年4月开源加密货币,它着重于隐私.分权和可扩展性.与自比特币衍生的许多加密货币不同,Monero基于CryptoNot ...

  6. PHP开发者常用的正则表达式及实例【长期更新收录】

    正则表达式在程序开发中是非常有用的,用好正则我们可以搜索.验证及替换文本或任何类型的字符.在这篇文章中,UncleToo为大家搜集了15个开发过程中常用的PHP正则表达式.函数及PHP示例,学习这些你 ...

  7. Mtcnn进行人脸剪裁和对齐

    from scipy import misc import tensorflow as tf import detect_face import cv2 import matplotlib.pyplo ...

  8. 《HelloGitHub》第 37 期

    公告 欢迎熟悉 C# 热爱开源的小伙伴加入我们,点此联系我 <HelloGitHub>第 37 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub ...

  9. UML用法及状态图,活动图介绍

    统一建模语言UML(Unified Modeling Language)是非专利的第三代建模和规约语言.UML是一种开放的方法,用于说明.可视化.构建和编写一个正在开发的.面向对象的.软件密集系统的制 ...

  10. 10位时间戳使用moment转化为日期

    前情提要: 需要把后台传过来的10位时间戳转化格式为:‘YYYY-MM-DD HH:mm:ss’的日期展示在页面上.本来是自己写了个函数,但是奈何leader说我们项目用了moment了,你为什么不用 ...