前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >单例设计模式

单例设计模式

作者头像
喜欢天文的pony站长
发布于 2020-06-29 04:07:35
发布于 2020-06-29 04:07:35
33000
代码可运行
举报
文章被收录于专栏:RabbitMQ实战RabbitMQ实战
运行总次数:0
代码可运行

# 单例模式需要满足:

  1. 私有的构造函数
  2. 懒加载
  3. 线程安全
  4. 通过静态方法来访问实例
  5. 无法通过反射来实例化对象
  6. 无法通过反序列化来实例化对象

1. 饿汉模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.futao.springbootdemo.design.pattern.gof.a.singleton;

/**
 * 单例模式1-饿汉模式,即在类加载的时候就实例化对象。
 *
 * @author futao
 * Created on 2018-12-25.
 */
public class EagerSingleton {
    /**
     * 因为该字段是静态的,属于类,所以会在类加载的时候就初始化,
     * 又因为类加载的时候是天然的线程安全的,所以不会有线程安全问题
     * <p>
     * 伴随着类的加载而实例化一个对象,如果该单例最后并未被使用,则浪费了系统资源
     */
    private static final EagerSingleton instance = new EagerSingleton();

    /**
     * 私有构造方法,防止用户随意new对象
     */
    private EagerSingleton() {
    }

    /**
     * 获取单例的静态方法,对于需要频繁访问的对象使用这种方式比较好
     * 为什么不设置成final的,因为静态方法没必要设置成final的
     * 
     * @return 单例
     */
    public static EagerSingleton getInstance() {
        return instance;
    }
}

2. 懒汉模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.futao.springbootdemo.design.pattern.gof.a.singleton;

import java.io.Serializable;

/**
 * 单例模式2-懒汉模式
 * 只有在用到的时候才实例化对象
 *
 * @author futao
 * Created on 2018-12-25.
 */
public class LazySingleton {
    private static LazySingleton instance;

    /**
     * 私有构造方法
     */
    private LazySingleton() {}

    /**
     * 会有线程安全问题,所以需要加上同步锁synchronized
     * 因为这种方式,同时只能被一个线程访问,其他线程都会被阻塞,所以多线程环境下获取对象的速度非常慢
     *
     * @return 单例对象
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

3. 枚举模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.futao.springbootdemo.design.pattern.gof.a.singleton;

/**
 * 单例模式3-枚举式
 * 避免了反射与反序列化的漏洞
 * 但是没有懒加载的效果
 *
 * @author futao
 * Created on 2018-12-25.
 */
public enum SingletonEnum {

    /**
     * 这个枚举元素本身就是单例的
     */
    INSTANCE;

    private int field;

    /**
     * 枚举也可以有普通成员方法
     *
     * @param words
     */
    public void say(String words) {
        System.out.println(words);
    }

    public int getField() {
        return field;
    }

    public void setField(int field) {
        this.field = field;
    }}

4. 静态内部类模式(静态内部类实现的单例无法防止反射)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.futao.springbootdemo.design.pattern.gof.a.singleton.byself;

/**
 * 单例模式4-静态内部类
 * 线程安全,调用效率高,并且实现了延时加载
 *
 * @author futao
 * Created on 2019-04-03.
 */
public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {
    }

    /**
     * 静态内部类并不会在类一开始加载的时候就加载
     * 要等到真正调用的时候才会加载
     * 又因为类加载是天然的线程安全的,所以不会有线程安全问题
     */
    private static class StaticInnerClass {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return StaticInnerClass.instance;
    }
}


=================================================
测试内部静态类的加载时机
=================================================

package com.futao.springbootdemo.design.pattern.gof.a.singleton;

import org.apache.commons.lang3.StringUtils;

/**
 * 测试内部静态类的加载时机
 *
 * @author futao
 * Created on 2019-04-02.
 */
public class InnerStaticClassLoaderOrder {
    static {
        System.out.println("外部类被加载");
    }

    public void outerMethod() {
        System.out.println("调用外部类方法");
    }

    /**
     * 内部静态类
     */
    private static class InnerStaticClass {

        static int a;
        int b;

        static {
            System.out.println("内部类被加载了");
        }

        public void innerMethod() {
            System.out.println("调用静态类内部方法");
        }
    }

    /**
     * 内部类
     */
    private class InnerClass {
        //        static int a;//普通内部类不允许有静态成员
        int b;

        public void innerClassMethod() {
            System.out.println("println");
        }
    }

    public static void main(String[] args) {
        InnerStaticClassLoaderOrder i = new InnerStaticClassLoaderOrder();
        i.outerMethod();
        System.out.println(StringUtils.repeat("==", 30));
        //静态内部类通过new 外部类类名.内部类类名()的方式实例化对象
        InnerStaticClassLoaderOrder.InnerStaticClass innerStaticClass = new InnerStaticClassLoaderOrder.InnerStaticClass();
        innerStaticClass.innerMethod();

        /*
        输出为:外部类被加载
                调用外部类方法
                ============================================================
                内部类被加载了
                调用静态类内部方法


                说明内部静态类不会随着外部类的加载而加载,而是等到被实际调用的时候才加载
                         */

        InnerClass innerClass = i.new InnerClass();//普通内部类只能通过外部类对象.new 内部类()来实例化对象
        innerClass.innerClassMethod();
    }
}

# 测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
    public void test75() {
        System.out.println(EagerSingleton.getInstance());
        System.out.println(EagerSingleton.getInstance());
        System.out.println(EagerSingleton.getInstance());
        System.out.println(StringUtils.repeat("==", 30));
        System.out.println(LazySingleton.getInstance());
        System.out.println(LazySingleton.getInstance());
        System.out.println(LazySingleton.getInstance());
        System.out.println(StringUtils.repeat("==", 30));
        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
        System.out.println(StringUtils.repeat("==", 30));
        System.out.println(StaticInnerClassSingleton.getInstance());
        System.out.println(StaticInnerClassSingleton.getInstance());
        System.out.println(StaticInnerClassSingleton.getInstance());
    }
# 如何防止反射来实例化对象
  • 首先看看如何通过反射创建一个对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SuppressWarnings("unchecked")
    @Test
    public void test76() throws Exception {
        //通过静态方法访问单例对象
        System.out.println(EagerSingleton.getInstance());
        System.out.println(EagerSingleton.getInstance());

        Class<EagerSingleton> eagerSingleton = (Class<EagerSingleton>) Class.forName("com.futao.springbootdemo.design.pattern.gof.a.singleton.EagerSingleton");
        //获取构造方法
        Constructor<EagerSingleton> constructor = eagerSingleton.getDeclaredConstructor();
        //因为构造方法是私有的,所以需要跳过java安全检查
        constructor.setAccessible(true);
        //通过反射创建新的对象
        EagerSingleton singleton = constructor.newInstance();
        System.out.println(singleton);
    }
  • 这样就破坏了对象的单例。但是从中可以看出,反射是通过调用构造方法来实例化对象的,所以考虑在构造方法进行拦截。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * 私有构造方法,防止用户随意new对象
     */
    private EagerSingleton() {
        if (instance != null) {
            //如果单例对象已经被创建,则不允许再调用构造方法创建对象
            throw new RuntimeException("不允许通过反射创建对象!");
        }
    }
# 如何防止反序列化来实例化对象
  • 如何通过反序列化来创建一个对象

首先如果要序列化与反序列化需要 implementsSerializable

  • 序列化对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//序列化对象
    @Test
    public void test77() throws Exception {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./a.txt"));
        objectOutputStream.writeObject(EagerSingleton.getInstance());
    }
  • 反序列化对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
    public void test77() throws Exception {
        //通过静态方法访问单例对象
        System.out.println(EagerSingleton.getInstance());
        System.out.println(EagerSingleton.getInstance());
        //反序列化对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./a.txt"));
        EagerSingleton eagerSingleton = (EagerSingleton) objectInputStream.readObject();
        System.out.println(eagerSingleton);
    }
  • 反序列化破坏单例的解决方案 在单例类中添加方法readResolve()
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * 防止反序列化创建对象
     * 在jdk中ObjectInputStream的类中有readUnshared()方法,
     * 如果被反序列化的对象的类存在readResolve这个方法,
     * 他会调用这个方法来返回一个“array”
     * 然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
     *
     * @return
     */
    private Object readResolve() {
        return instance;
    }
  • 再次执行上面的测试

# 各种单例模式效率测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
    public void test74() throws InterruptedException {
        int threadCount = 10;
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        //开启10个线程
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                //10个线程并发获取单例对象1000W次
                for (int j = 0; j < 10000000; j++) {
                    Object o = EagerSingleton.getInstance();
                }
                //一个线程执行完成之后计数器-1
                countDownLatch.countDown();
            }).start();
        }
        //阻塞主线程进行等待,内部会一直检查计数器的值是否为0
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
  • 饿汉模式
  • 懒加载模式
  • 枚举模式
  • 静态内部类模式

单例模式名称

测试线程数

单个线程访问对象次数

耗时

饿汉模式

10

1000W

165ms

懒汉模式

10

1000W

5750ms

枚举式

10

1000W

120ms

静态内部类

10

1000W

115ms

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 喜欢天文 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
WPF 修改CheckBox样式
它包含一个复选框(ToggleButton)和一个文(Content),改写它,要做的就是修改它们的模板了~
zls365
2021/04/02
2.7K0
有几十个WPF设计和动画的项目
这是一个 WPF 项目,用于创作及收集一些好玩的设计和动画。目前已有数十个 Demo,部分 Demo 有相关博客介绍详细的实现步骤和原理:
沙漠尽头的狼
2022/04/18
1.3K0
有几十个WPF设计和动画的项目
[WPF] 实现两个任天堂 Switch 的加载动画
写了两个 Switch 的加载动画,第一个是 Swtich 最常见那个 Loading 动画:
dino.c
2021/12/10
6090
[WPF] 实现两个任天堂 Switch 的加载动画
代码创建 WPF 旋转、翻转动画(汇总)
先建立一个button <Button Width="80" Height="60" Content="旋转" Name="trans" Click="trans_Click" Style="{x:Null}"/> 方法一:绕左上角旋转 public void Transform1() { RotateTransform rtf = new RotateTransform(); trans.RenderTransform
hbbliyong
2018/03/05
3.5K0
代码创建 WPF 旋转、翻转动画(汇总)
WPF中触发器Trigger、MultiTrigger、DataTrigger、MultiDataTrigger
WPF中有种叫做触发器的东西(记住不是数据库的trigger哦)。它的主要作用是根据trigger的不同条件来自动更改外观属性,或者执行动画等操作。
zls365
2021/01/28
3.4K0
Silverlight之ListBox/Style学习笔记--ListBox版的图片轮换广告
ListBox是一个很有用的控件,其功能直逼Asp.Net中的Repeater,它能实现自定义数据项模板,纵向/横向排列Item(如果扩展一下实现自行折行,几乎就是SL版的Repeater了--实际上WrapPanel已经实现了,不过没有默认集成在SL3中).  这里推荐一个老外的文章 http://blogs.msdn.com/delay/archive/2008/03/05/lb-sv-faq-examples-notes-tips-and-more-for-silverlight-2-beta-1-s
菩提树下的杨过
2018/01/22
1K0
Silverlight之ListBox/Style学习笔记--ListBox版的图片轮换广告
win10 uwp 修改Pivot Header 颜色
参见: http://stackoverflow.com/questions/31797875/overriding-pivot-header-foreground-brushes-in-uwp-app-win-10-rtm-sdk
林德熙
2018/09/19
6580
win10 uwp 修改Pivot Header 颜色
WPF TreeGrid MVVM 模式下自定义表格带展开缩放效果,并且可以获取点击行的数据
MVVM其实就是:Model 、View、ViewModel三个的简称,就像MVC一样。 Model就是模型。View就是视图。ViewModel就是和view进行绑定的。
Shunnet
2022/05/31
6K0
WPF TreeGrid MVVM 模式下自定义表格带展开缩放效果,并且可以获取点击行的数据
win10 UWP 圆形等待
使用 RectangleStyle1 在 Rectangle 使用 style="{StaticResource RectangleStyle1}"
林德熙
2018/09/18
4100
win10 UWP 圆形等待
[WPF 自定义控件]自定义一个“传统”的 Validation.ErrorTemplate
数据绑定模型允许您将与您Binding的对象相关联ValidationRules。 如果用户输入的值无效,你可能希望在应用程序 用户界面 (UI) 上提供一些有关错误的反馈。 提供此类反馈的一种方法是设置Validation.ErrorTemplate附加到自定义ControlTemplate的属性。
dino.c
2020/03/02
1.5K0
Silverlight的自定义tooltip提示工具条
这种应用场景其实很多,比如游戏中装备/魔法的选择菜单,这里借用了"深蓝色右手"的一张图  再比如聊天室中的文本颜色设置  虽然sl的ToolTipService.ToolTip属性可以设置任何对象,比
菩提树下的杨过
2018/01/23
1.3K0
Silverlight的自定义tooltip提示工具条
WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True)
在 WPF 中,如果想做一个背景透明的异形窗口,基本上都要设置 WindowStyle="None"、AllowsTransparency="True" 这两个属性。如果不想自定义窗口样式,还需要设置 Background="Transparent"。这样的设置会让窗口变成 Layered Window,WPF 在这种类型窗口上的渲染性能是非常糟糕的。
walterlv
2023/10/22
1.8K0
WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True)
[UWP]理解及扩展Expander
最近在自定义Expander的样式,顺便看了看它的源码。 Expander控件是一个ContentControl,它通过IsExpanded属性或者通过点击Header中的ToggleButton控制内容展开或隐藏。UWP SDK中没提供这个控件,而是在UWP Community Toolkit中 提供 。它是个教科书式的入门级控件,代码简单,虽然仍然不尽如人意,但很适合用于学习如何自定义模版化控件。
dino.c
2019/01/18
8710
[UWP]理解及扩展Expander
WPF 和 UWP 中,不用设置 From 或 To,Storyboard 即拥有更灵活的动画控制
发布于 2017-10-26 04:55 更新于 2018-02-19 22:41
walterlv
2018/09/18
1.2K0
WPF 和 UWP 中,不用设置 From 或 To,Storyboard 即拥有更灵活的动画控制
WPF加载中实现
加载页面 XAML <Window x:Class="SchoolClient.Wins.LoadDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="100" Height="100" AllowsTransparency="True"
码客说
2020/07/27
9680
[Silverlight]用ListBox实现SlideShow
用Silverlight2整整一年了,上个星期公司全面转去Silverlight3,作为纪念就把用SL2写的最后一个东西发出来吧。效果如下:
dino.c
2019/01/18
5960
[Silverlight]用ListBox实现SlideShow
WPF 修改 ItemContainerStyle 鼠标移动到未选中项效果和选中项背景
本文告诉大家如何通过修改 ItemContainerStyle 让 ListView 或 ListBox 的选择效果如鼠标移动到未选中项的效果或选择项的背景
林德熙
2019/03/13
2.4K0
WPF 修改 ItemContainerStyle 鼠标移动到未选中项效果和选中项背景
WPF 制作 Windows 屏保
[1]GitHub: https://github.com/yanjinhuagood/ScreenSaver
独立观察员
2022/12/06
9480
WPF 制作 Windows 屏保
[UWP]用Shape做动画
相对于WPF/Silverlight,UWP的动画系统可以说有大幅提高,不过本文无意深入讨论这些动画API,本文将介绍使用Shape做一些进度、等待方面的动画,除此之外也会介绍一些相关技巧。
dino.c
2019/01/18
2K0
[UWP]用Shape做动画
sliverlight:按点击移动的连续跑马灯
演示效果如下: 鼠标点击一次,所有方块向左移动一格 思路: 1.利用StoryBoard产生移动,每次点击时,该动画播放一次(即移动一格) 2.关键:动画播放完毕后,调用Stop方法,以回到初始位置,
菩提树下的杨过
2018/01/23
7710
sliverlight:按点击移动的连续跑马灯
推荐阅读
相关推荐
WPF 修改CheckBox样式
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • # 单例模式需要满足:
  • 1. 饿汉模式
  • 2. 懒汉模式
  • 3. 枚举模式
  • 4. 静态内部类模式(静态内部类实现的单例无法防止反射)
  • # 测试
    • # 如何防止反射来实例化对象
    • # 如何防止反序列化来实例化对象
  • # 各种单例模式效率测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文