如何构建创造性设计模式:单例模式

单例设计模式是一种软件设计模式,它将类的实例化限制为一个对象。与其他创造性设计模式(如抽象工厂)相比,单例构建器模式将创建一个对象,并且还将负责只存在该对象的一个实例。

当创建一个单例类时,有一些问题需要记住:

如何确保一个类只有一个实例?

如何方便地访问类的惟一实例?

类如何控制实例化?

如何限制类的实例数量?

例如,假设我们有一个发送消息的类 — the Messenger class:

package com.gkatzioura.design.creational.singleton;

public class Messenger {

    public void send(String message) {
    }
}

但是,我们希望消息过程仅由Messenger类的一个实例来处理。让我们假设一个场景:Messenger类打开一个tcp连接(例如,XMPP),并且为了发送消息,必须保持连接的存在。每次必须发送消息时,打开新的XMPP连接会非常低效。

因此,我们将继续并使Messenger类成为单例。

package com.gkatzioura.design.creational.singleton;

public class Messenger {

    private static Messenger messenger = new Messenger();

    private Messenger() {}

    public static Messenger getInstance() {
        return messenger;
    }

    public void send(String message) {
    }
}

如您所见,我们将messenger构造函数设置为private,并使用静态变量初始化一个messenger。静态变量是类级变量,其中内存分配只在类被加载到内存中时发生一次。在此过程中,我们确保Messenger类将只实例化一次。getInstance方法将在调用静态messenger实例时获取它。

显然,前面的方法有其优点和缺点,我们不必担心线程安全性,并且只有在加载Messenger类时才会创建实例。但是,它缺乏灵活性。让我们考虑将配置变量传递给Messenger构造函数的场景。您将发现使用以前的方法是不可能的。

解决方案是在getInstance方法上实例化Messenger类。

package com.gkatzioura.design.creational.singleton.lait;

public class Messenger {

    private static Messenger messenger;

    private Messenger() {}

    public static Messenger getInstance() {

        if(messenger==null) {
            messenger = new Messenger();
        }

        return messenger;
    }

    public void send(String message) {

    }
}

上述方法在某些情况下可能有效,但在多线程环境中实例化类的情况下,它会忽略线程安全性。

使类线程安全的最简单方法是同步getInstance方法。

package com.gkatzioura.design.creational.singleton.lait;

public class Messenger {

    private static Messenger messenger;

    private Messenger() {}

    public synchronized static Messenger getInstance() {

        if(messenger==null) {
            messenger = new Messenger();
        }

        return messenger;
    }

    public void send(String message) {

    }
}

上面的代码可以正常工作。至少,messenger的创建将是同步的,不会创建重复的副本。这种方法的问题是,只有在创建对象时才需要同步。使用上述代码将导致不必要的开销。

另一种方法是使用双重检查锁定方法。现在,双重检查锁定需要特别小心,因为很容易在错误的实现中选择正确的实现。最好的方法是使用volatile关键字实现延迟加载。

package com.gkatzioura.design.creational.singleton.dcl;

public class Messenger {

    private static final Object lock = new Object();
    private static volatile Messenger messenger;

    private Messenger() {}

    public static Messenger getInstance() {

        if(messenger==null) {
            synchronized (lock) {
                if(messenger==null) {
                    messenger = new Messenger();
                }
            }
        }

        return messenger;
    }

    public void send(String message) {

    }
}

通过使用volatile关键字,我们可以防止volatile对象的写入对任何先前的读写进行重新排序,防止volatile的读取对任何后续的读写进行重新排序。另外,互斥对象用于实现同步。

总之,我们创建了一个对象,并确保该对象只有一个实例。我们确保在多线程环境中实例化对象不会出现任何问题。

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-07-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏令仔很忙

工厂模式的Assembly.Load(path).CreateInstance(className)出错解决方法

  下面咱们先了解Assembly.Load(path).CreateInstance(className)

12020
来自专栏武培轩的专栏

Keep面经汇总

原理:泛型的实现是靠类型擦除技术,类型擦除是在编译期完成的,在编译期,编译器会将泛型的类型参数都擦除成它的限定类型,如果没有则擦除为object类型之后在获取的...

13930
来自专栏FreeBuf

Python黑客学习笔记:从HelloWorld到编写PoC(上)

本系列文章适合CS在读学生和万年工具党,本文会在英文原文的基础上做些修改,并适当增加些解释说明。 ? 本篇包含原文的前几部分: 0x0 – Getting St...

321100
来自专栏公众号_薛勤的博客

Java多线程编程核心技术(三)多线程通信

通过本节可以学习到,线程与线程之间不是独立的个体,它们彼此之间可以互相通信和协作。

13880
来自专栏java初学

java线程安全— synchronized和volatile

379170
来自专栏安恒网络空间安全讲武堂

WriteUp分享 | CTF-web

题目 各种绕过哦 TXT? 文件上传测试 本地包含 考细心 正则? PHP很烦人? 一道签到题 抽抽奖 never give up I have a j...

3.7K80
来自专栏技巅

Redis第一个版本源码分析-启动过程分析1

19850
来自专栏腾讯IVWEB团队的专栏

ECMAScript 2015 (ES6) in Node.js(译)

Node.js是建立在V8引擎的基础上。通过保持对该引擎最新发布版的更新,我们可以确保能够将JavaScript ECMA-262 specification中...

18800
来自专栏大内老A

[ASP.NET MVC]如何定制Numeric属性/字段验证消息

对于一个Numeric属性/字段,ASP.NET MVC会自动进行数据类型的验证(客户端验证),以确保输入的是一个有效的数字,但是呈现在页面上的错误消息总是一段...

240100
来自专栏dotnet & java

WCF 入门(20)

今天第20集了。这个视频系列里面有6集和异常相关,这集是最后一集。前面几集讲了服务端遇到普通的 .net exception时候,要转换城Soap Fault,...

7030

扫码关注云+社区

领取腾讯云代金券