单例模式的优化之路(java)
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)的更多相关文章
- 我的阿里之路+Java面经考点
我的阿里之路+Java面经考点 时间:2018-03-19 23:03 来源:未知 作者:admin 点击:87次 我的2017是忙碌的一年,从年初备战实习春招,年三十都在死磕JDK源码,三 ...
- 阿里巴巴 web前端性能优化进阶路
Web前端性能优化WPO,相信大多数前端同学都不会陌生,在各自所负责的站点页面中,也都会或多或少的有过一定的技术实践.可以说,这个领域并不缺乏成熟技术理论和技术牛人:例如Yahoo的web站点性能优化 ...
- JVM性能优化系列-(1) Java内存区域
1. Java内存区域 1.1 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.主要包括:程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运 ...
- 微博MySQL优化之路--dockone微信群分享
微博MySQL优化之路 数据库是所有架构中不可缺少的一环,一旦数据库出现性能问题,那对整个系统都回来带灾难性的后果.并且数据库一旦出现问题,由于数据库天生有状态(分主从)带数据(一般还不小),所以出问 ...
- 新浪微博iOS客户端架构与优化之路
新浪微博iOS客户端架构与优化之路 随着Facebook.Twitter.微博的崛起,向UGC.PGC.OGC,自媒体提供平台的内 容消费型App逐渐形成了独特的客户端架构模式.与电商和通讯工具类 ...
- Linkedin工程师是如何优化他们的Java代码的(转)
英文原文:LinkedIn Feed: Faster with Less JVM Garbage 最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客上面发现了一篇很不错博文.这篇博文介绍 ...
- Golang 优化之路——bitset
写在前面 开发过程中会经常处理集合这种数据结构,简单点的处理方法都是使用内置的map实现.但是如果要应对大量数据,例如,存放大量电话号码,使用map占用内存大的问题就会凸显出来.内存占用高又会带来一些 ...
- Linkedin工程师是如何优化他们的Java代码的
http://greenrobot.me/devpost/java-faster-less-jvm-garbage/ Linkedin工程师是如何优化他们的Java代码的 最近在刷各大公司的技术博客的 ...
- Java 性能优化手册 — 提高 Java 代码性能的各种技巧
转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...
随机推荐
- 全国省市县区域信息最新数据库脚本(mysql版本)
/*Navicat MySQL Data Transfer Source Server : localhostSource Server Version : 50717Source Host : lo ...
- 彻底弄懂HTTP缓存机制及原理
前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能. 但是对于很多前端 ...
- Volley 图片加载相关源码解析
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...
- 解决vi上下左右变ABCD问题
第一步执行sudo apt-get install vim,如果没有出现错误,再次进入vi 尝试一下,看看有没有修改过来,如果出现以下错误E: Package 'vim' has no insta ...
- TestNG深入理解
以下内容引自: http://blog.csdn.net/wanglha/article/details/42004695 TestNG深入理解 转载 2014年12月18日 13:56:11 参考文 ...
- TProfiler部署文档--笔记
TProfiler是一个可以在生产环境长期使用的性能分析工具.它同时支持剖析和采样两种方式,记录方法执行的时间和次数,生成方法热点 对象创建热点 线程状态分析等数据,为查找系统性能瓶颈提供数据支持. ...
- BZOJ_1717_[Usaco2006 Dec]Milk Patterns 产奶的模式_后缀数组
BZOJ_1717_[Usaco2006 Dec]Milk Patterns 产奶的模式_后缀数组 Description 农夫John发现他的奶牛产奶的质量一直在变动.经过细致的调查,他发现:虽然他 ...
- BZOJ_2427_[HAOI2010]软件安装_tarjan+树形DP
BZOJ_2427_[HAOI2010]软件安装_tarjan+树形DP 题意: 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一些软件安装到一台磁 ...
- COGS2421 [HZOI 2016]简单的Treap
题面见这里 大概是个模板题 Treap暴力插入的做法太暴力了并不优美 这里就需要用到笛卡尔树的构造方法,定义见这里 在 假的O(n) 的时间内构造一棵Treap 把元素从小到大排序 这样从小到大插入时 ...
- hystrix 请求合并(6)
hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销.重要一点,两个请求能 ...