Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

27 | 单例模式:如何创建单一对象优化系统性能?

刘超 2019-07-23
你好,我是刘超。
从这一讲开始,我们将一起探讨设计模式的性能调优。在《Design Patterns: Elements of Reusable Object-Oriented Software》一书中,有 23 种设计模式的描述,其中,单例设计模式是最常用的设计模式之一。无论是在开源框架,还是在我们的日常开发中,单例模式几乎无处不在。

什么是单例模式?

它的核心在于,单例模式可以保证一个类仅创建一个实例,并提供一个访问它的全局访问点。
该模式有三个基本要点:一是这个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
结合这三点,我们来实现一个简单的单例:
//饿汉模式
public final class Singleton {
private static Singleton instance=new Singleton();//自行创建实例
private Singleton(){}//构造函数
public static Singleton getInstance(){//通过该函数向整个系统提供实例
return instance;
}
}
由于在一个系统中,一个类经常会被使用在不同的地方,通过单例模式,我们可以避免多次创建多个实例,从而节约系统资源。

饿汉模式

我们可以发现,以上第一种实现单例的代码中,使用了 static 修饰了成员变量 instance,所以该变量会在类初始化的过程中被收集进类构造器即 <clinit> 方法中。在多线程场景下,JVM 会保证只有一个线程能执行该类的 <clinit> 方法,其它线程将会被阻塞等待。
等到唯一的一次 <clinit> 方法执行完成,其它线程将不会再执行 <clinit> 方法,转而执行自己的代码。也就是说,static 修饰了成员变量 instance,在多线程的情况下能保证只实例化一次。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(26)

  • Loubobooo
    使用枚举来实现单例模式,具体代码如下:public class SinletonExample5 {
        private static SinletonExample5 instance = null;

        // 私有构造函数
        private SinletonExample5(){
        }

        public static SinletonExample5 getInstance(){
            return Sinleton.SINLETON.getInstance();
        }

        private enum Sinleton{
            SINLETON;

            private SinletonExample5 singleton;

            // JVM保证这个方法只调用一次
            Sinleton(){
                singleton = new SinletonExample5();
            }

            public SinletonExample5 getInstance(){
                return singleton;
            }
        }
    }

    作者回复: 很赞!这是一种懒加载模式的枚举实现。

    2019-07-23
    1
    23
  • 豆泥丸
    最安全的枚举模式,反射和序列化都是单例。

    作者回复: 对的,我们之前序列化优化这一讲中的问答题就是与枚举实现单例相关,《Effective Java》作者也是强烈推荐枚举方式实现单例。

    2019-07-23
    11
  • 行者
    枚举也是一种单例模式,同时是饿汉式。
    相比Double Check,以内部类方式实现单例模式,代码简洁,性能相近,在我看来是更优的选择。

    作者回复: 也可以使用枚举实现懒汉模式,可以根据本讲中的使用内部类方式实现懒加载。

    2019-07-23
    4
  • -W.LI-
    枚举底层实现就是静态内部类吧

    作者回复: 对的,枚举是一种语法糖,在Java编译后,枚举类中的枚举会被声明为static,接下来就跟我们文中讲的一样了。

    2019-07-23
    4
  • 我又不乱来
    枚举天生就是单例,但是不清楚这么实现。
    注册式单例,spring应该是用的这种。这个也不太清楚,超哥有机会讲一下spring的实现方式和枚举方式实现的单例。谢谢😁

    作者回复: Spring中的bean的单例虽然是一种单例效果,但实现方式是通过容器缓存实现,严格来说是一种享元模式。

    2019-07-23
    4
  • Jxin
    1.可能大部分同学都知道,但为了少部分同学,我在老师这个单例上补个点。其它线程空指针异常确实是指令重排导致的,但其原因还有一个。加锁并不能阻止cpu调度线程执行体,所以时间片还是会切的(假设单核),所以其他线程依旧会执行锁外层的if(),并发情况下就可能拿到仅赋值引用,未在内存空间存储数据的实例(null实例),进而空指针。
    2.给老师的代码补段骚的:
    // 懒汉模式 + synchronized 同步锁 + double-check
    public final class Singleton {
        private static validate Singleton instance = null;// 不实例化
        public List<String> list;//list 属性
        private Singleton(){
          list = new ArrayList<String>();
        }// 构造函数
        public static Singleton getInstance(){// 加同步锁,通过该函数向整个系统提供实例
            Singleton temp = instance;
            if(null == temp){// 第一次判断,当 instance 为 null 时,则实例化对象,否则直接返回对象
              synchronized (Singleton.class){// 同步锁
                 temp = instance;
                 if(null == temp){// 第二次判断
                    temp = new Singleton();// 实例化对象
                    instance = temp;
                 }
              }
            }
            return instance;// 返回已存在的对象
        }
    }
    用临时变量做方法内数据承载(相对于validate修饰的属性,可以减少从内存直接拷贝数据的次数),最后用instance接收临时变量时,因为是validate修饰,所以也不会有指令重排。所以前面临时变量的赋值操作已经完成,这样instance就必然是赋值好的实例。(如有错误请老师指出,仅个人理解的骚操作)

    3.极限编程试试就好,业务代码还是尽量优先保证可读性,只有在有性能需求时再采用影响可读性的性能优化。我的这种骚写法和老师的内部类,这种看起来需要想那么一下的东西尽量避免,简单才是王道。

    作者回复: 虽然有点绕,还是值得表扬的。我们还是鼓励简单易懂的编程风格。

    2019-07-23
    1
    3
  • 我知道了嗯
    枚举实现单例

    作者回复: 对的

    2019-07-23
    2
  • 张三丰
    如果把这个成员变量的static去掉,在多线程情况下就可能创建多个实例,单线程没问题。老师,这么理解没问题吧?


    // 饿汉模式
    public final class Singleton {
        private Singleton instance=new Singleton();// 自行创建实例
        private Singleton(){}// 构造函数
        public static Singleton getInstance(){// 通过该函数向整个系统提供实例
            return instance;
        }
    }

    作者回复: 这个没法调用的哦,静态方法无法调用非静态成员变量。

    2019-10-22
    1
  • QQ怪
    这一节虽然都懂,但是评论区补充的我还是第一次见到,get到了,有收获,哈哈

    作者回复: 互相学习,共同进步

    2019-07-23
    1
  • Zed
    容器类管理

    class InstanceManager {
        private static Map<String, Object> objectMap = new HashMap<>();
        private InstanceManager(){}
        public static void registerService(String key,Object instance){
            if (!objectMap.containsKey(key)){
                objectMap.put(key,instance);
            }
        }
        public static Object getService(String key){
            return objectMap.get(key);
        }
    }

    作者回复: Spring中bean的单例就是使用容器来实现的,便于管理。

    2019-07-23
    2
    1
  • ylw66
    👍,看过的讲singleton最好的文章
    2019-10-28
  • 风轻扬
    老师,您在内部类实现单例的例子的中。在私有构造方法中,手动new了一个ArrayList集合。在后面的方法中并没有使用这个list,这个list实例是干嘛用的

    作者回复: 可以忽略,这个属性在这里只是用作重排序的问题

    2019-09-14
  • godtrue
    不错,以为自己懂了的东西,其实深挖之后发现还有许多自己从未想到的宝贝。
    感谢,老师及其他同学的分享!
    内部类实现的懒汉模式,代码简单性能优越也不存在线程安全问题,值得尝试。
    2019-09-12
  • 风轻扬
    老师。枚举单例可以防止反射攻击、序列化攻击。但是,我们要获取的实例化对象怎么防止暴力反射呢?我现在的做法是在实例化对象的私有构造器中加判断,如果暴力反射,直接抛出运行异常。老师有没有好的办法?百思不得其解

    作者回复: 可以通过反序列化对象白名单来控制运行反序列哪些对象,这种方式需要重写resolveClass 方法,具体参考09讲:

    极客时间版权所有: https://time.geekbang.org/column/article/99774

    2019-09-11
  • 疯狂咸鱼
    老师,赵饿汉模式的写法,java 中static变量可以保证线程安全了?什么情况下可以呢

    作者回复: 不行,这里指的是加载时是线程安全的。

    2019-09-01
  • rong
    大佬,请教个问题,“这种方式实现的单例模式,在类加载阶段就已经在堆内存中开辟了一块内存,用于存放实例化对象,所以也称为饿汉模式。”, 饿汉模式下,类加载阶段,不会执行static代码块和成员变量,初始化阶段的时候才会执行。所以,类加载阶段,不会开辟内存吧?

    作者回复: 对的,已纠正

    2019-08-15
  • 路西法
    // 懒汉模式 + synchronized 同步锁 + double-check
    public final class Singleton {
        private volatile static Singleton instance= null;// 不实例化
        public List<String> list = null;//list 属性
        private Singleton(){
          list = new ArrayList<String>();
        }// 构造函数
        public static Singleton getInstance(){// 加同步锁,通过该函数向整个系统提供实例
            if(null == instance){// 第一次判断,当 instance 为 null 时,则实例化对象,否则直接返回对象
              synchronized (Singleton.class){// 同步锁
                 if(null == instance){// 第二次判断
                    instance = new Singleton();// 实例化对象
                 }
              }
            }
            return instance;// 返回已存在的对象
        }
    }



    这里不需要加 volatile

    synchronized 遵循 happens before 原则,即 在 synchronized 块里的写,对后续其它线程的读是可见的。

    作者回复: 这里的volatile 是用来防止重排序

    2019-08-12
  • 莫观生
    超哥,jvm要在代码被执行一定次数之后才会触发即时编译,也就是getInstance方法需要触发多次才会触发即时编译导致指令重排,但是getInstance被执行一次后单例对象就已经被初始化了,理论上是不应该出现指令重排的问题?求解惑

    作者回复: 即时编译并非运行多次代码才会触发,java将字节码转为机器码时就是即时编译

    2019-08-04
    1
  • 发条橙子 。
    老师 ,最后实现的懒汉模式有一个地方不太懂。 在类加载的时候,内部类不会跟着一起加载么?

    我之前以为会将内部类一起加载,开辟一块内存。这样其他地方引用该内部类的时候才可以在解析阶段把符号引用转为地址引用。

    但是看老师的例子,应该是在外部调用内部类的时候才真正去加载

    作者回复: 是的

    2019-07-31
  • Aaron
    谢谢超哥分享,写了这么多年代码,也看了不少博客也写了不少博客,今天算是彻底搞懂了,很多时候都是觉得似乎懂了,自己做发现还有点模糊,工作忙反正平时都那么写没深究。get到了很多干活。谢谢🙏
    2019-07-28
收起评论
26
返回
顶部