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. git学习(持续踩坑中🤣)

    https://segmentfault.com/q/1010000002457936 常见指令: 一.创建版本库 $ mkdir learngit 创建文件夹 $ cd learngit 进入文件夹 ...

  2. jQuery学习之旅 Item1 选择器【一】

    点击"名称"会跳转到此方法的jQuery官方说明文档. 1. 基础选择器 Basics 名称 说明 举例 #id 根据元素Id选择 $("divId") 选择I ...

  3. Requests库作者另一神器Pipenv的用法

    前言 我们在运行 Python 项目的时候经常会遇到一些版本问题,例如 A 项目依赖于 Django 1.5,而 B 项目又依赖 Django 2.0,而我们的系统却只有一个 Python 解释器,我 ...

  4. 如何解决python升级后yum报错

    当我们yum命令的时候,会提示 "File "/usr/bin/yum", line 30 except KeyboardInterrupt, e: ^ SyntaxEr ...

  5. j2ee 使用db.properties连接mysql数据库

    转自: http://blog.csdn.net/u013815546/article/details/50808493 注: 下面的方法是未安装构架的写法,需要自己加载驱动并建立连接. 若引入了Ac ...

  6. iOS之LLDB常用调试命令

    LLDB是个开源的内置于XCode的调试工具,这里来理一理常用用法.lldb对于命令的简称,是头部匹配方式,只要不混淆,你可以随意简称某个命令.结果为在xcode下验证所得,可能与其它平台有所误差. ...

  7. 显著性检测(saliency detection)评价指标之KL散度距离Matlab代码实现

    步骤1:先定义KLdiv函数: function score = KLdiv(saliencyMap, fixationMap) % saliencyMap is the saliency map % ...

  8. jsp 基础知识之指令元素

    由于考研和结业的事情,这里荒废了许久,而如今重新捡起来,是因为带到公司的碳素笔没有油了......    jsp的指令元素:通常以<%@开始,以%>结尾. jsp主要包括三种指令元素:pa ...

  9. 我是庖丁,<肢解IOT平台>之物模型

    前言 物模型是对设备在云端的功能描述,包括设备的属性,数据,服务和事件. 物联网平台通过定义一种物的描述语言来描述物模型,称之为 TSL(即 Thing Specification Language) ...

  10. 经典Hash函数的实现

    Hash函数是指把一个大范围映射到一个小范围.把大范围映射到一个小范围的目的往往是为了节省空间,使得数据容易保存. 除此以外,Hash函数往往应用于查找上.所以,在考虑使用Hash函数之前,需要明白它 ...