博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式(三) 单例模式
阅读量:6441 次
发布时间:2019-06-23

本文共 3583 字,大约阅读时间需要 11 分钟。

单例模式也是一种创建型模式,而且也非常容易理解:在一个系统中可能需要多个配置文件,我们希望这些配置文件的实例只存在一个,而不是存在多个重复的实例。这时候就需要使用单例模式。

单例模式有几个要点:

  • 一是必须确保只存在一个类的实例。
  • 二是类必须自己创建自己,不允许其他类来创建自己。
  • 三是必须提供一个方法允许其他类访问单例成员。

根据这些特点,我们可以很容易猜出单例类在Java的样子:首先他的构造方法必须是私有的,然后往往需要一个公有的静态方法获取单例实例。

单例的实现

单例模式的实现有很多种,按照单例的实例化的时机可以分为饿汉式和懒汉式两种,下面来逐一说明。

懒汉式(非线程安全)

这种方式非常简单,也很容易理解。单例实例在第一次调用的时候才创建,符合懒加载的要求。唯一缺点是这种方式不支持多线程,在多线程环境下可能会创建多个对象。

public class UnThreadSafeSingleton {    private UnThreadSafeSingleton() {    }    private static UnThreadSafeSingleton singleton;    public static UnThreadSafeSingleton getSingleton() {        if (singleton == null) {            singleton = new UnThreadSafeSingleton();        }        return singleton;    }}

懒汉式(同步的)

我们可以对上面的实现方式进行改进,以便在多线程环境下也可以正常工作。实现方式很简单,直接在方法上添加synchronized关键字即可。

这种实现方式虽然也很简单,但是性能不咋地。由于直接在方法上加了锁,所以如果同时有两个地方获取单例对象,其中一个就会阻塞。在获取单例的次数获取比较多的时候性能很差。

public class SynchronizedThreadSafeSingleton {    private static SynchronizedThreadSafeSingleton singleton;    private SynchronizedThreadSafeSingleton() {    }    public synchronized static SynchronizedThreadSafeSingleton getSingleton() {        if (singleton == null) {            singleton = new SynchronizedThreadSafeSingleton();        }        return singleton;    }}

饿汉式(静态初始化)

如果不要求必须懒加载,那么我们可以使用JVM的类加载工作机制,方便的实现单例模式。

JVM在第一次加载类的时候,会被初始化累的静态域,并确保静态域只初始化一次。所以我们可以将创建单例的代码放到静态初始化块中,这样JVM会帮我们创建单例。这种方式的缺点就是加载类的时候就创建了单例对象,没有懒加载。

public class FirstLoadSingleton {    private static FirstLoadSingleton singleton;    private FirstLoadSingleton() {    }    static {        singleton = new FirstLoadSingleton();    }    public static FirstLoadSingleton getSingleton() {        return singleton;    }}

双检锁方式

这种方式比较复杂,但是其他方面都很好:既实现了懒加载,同时也是线程安全的,性能还不错。

双检锁模式的要点:一是单例必须使用volatile关键字标记;二是在创建单例的时候要进行两次检查(这就是双检锁的含义)。我们可以看到同步块在第一次判断之后,也就是说只有在第一次调用时才可能发生竞争和阻塞。单例创建之后,在获取单例的时候不会调用同步块,因此速度会非常快。和前面的直接在方法上添加同步的例子相比,真是不知道高到哪里去了。

public class DoubleCheckLockSingleton {    private volatile static DoubleCheckLockSingleton singleton;    private DoubleCheckLockSingleton() {    }    public static DoubleCheckLockSingleton getSingleton() {        if (singleton == null) {            synchronized (DoubleCheckLockSingleton.class) {                if (singleton == null) {                    singleton = new DoubleCheckLockSingleton();                }            }        }        return singleton;    }}

静态内部类方式

这种方式和双检锁方式的效果类似,既可以保证懒加载又具有多线程下的性能优势。而且实现起来更加简单。唯一缺点就是单例对象必须是静态的,而双检锁方式的单例对象可以是实例的。

道理也很简单,如果我们把单例放到类的静态字段上,不能保证延迟加载的话,那么再用一层内部类包住不就行了。这样,当外层类第一次加载的时候,不会触发单例的初始化。而在第一次获取单例的时候,才会调用内部类,从而让JVM加载单例。

public class InnerClassSingleton {    private static class Inner {        private static InnerClassSingleton singleton = new InnerClassSingleton();    }    private InnerClassSingleton() {    }    public static InnerClassSingleton getSingleton() {        return Inner.singleton;    }}

枚举方式

这种方式是Java实现单例最好的方式,连《Effective Java》都推荐我们使用这种方式。不过现在貌似使用的还是比较少。一来,枚举是Java 1.5才加入的东西;二来,Java的枚举使用起来确实很捉急。甚至有些开发实践都要求不使用枚举,而是使用共有静态字段来代替。所以枚举单例这种方式就比较稀少了。

不过确实,Java的枚举天生就是为实现单例而存在的。首先,枚举的实例是在使用时才被初始化的,这和单例模式延迟加载的要求相符。其次,枚举类型只允许存在私有的构造函数,从根本上杜绝了创建多个单例的可能性。而且当枚举序列化和反序列化的时候,同样会保证单例的唯一性。因此我们说,枚举方式是Java实现单例最好的方式。

可能还是不太好理解,所以还是直接看代码吧。假设我们需要一个单例的配置对象,我们可以创建枚举来解决。枚举的构造方法默认(且只能)是私有的,我们直接在构造方法中初始化数据(例如从文件读取等等),然后通过枚举类中定义的方法来读取数据。如果对Java的枚举还是感觉到比较陌生的话回去复习一下枚举类的用法。

public enum EnumSingleton {    Instance;    private String data;    EnumSingleton() {        //在构造方法中进行初始化        data = "Some data";    }    public String getData() {        return data;    }}

当然在现在的Java生态中单例模式一般不需要我们手动实现了。像Spring和Guice这样的依赖注入框架已经实现了单例模式,所以我们在使用这些框架的时候,创建和确保单例的工作有这些框架完成,我们只需要编写传统的非线程安全类即可。

转载地址:http://vscwo.baihongyu.com/

你可能感兴趣的文章
如何合理的规划jvm性能调优
查看>>
构建你的第一个Flutter视频通话应用
查看>>
114. Flatten Binary Tree to Linked List
查看>>
在react-router中进行代码拆分
查看>>
User Stories - 最佳实践 (Best Practices)
查看>>
Spring - Java-based configuration: Using @Configuration
查看>>
使用Envoy 作Sidecar Proxy的微服务模式-2.超时和重试
查看>>
node.js初体验之利用node.js的fs-文件系统,来写一个批量修改文件名的小工具
查看>>
数据流被污染?数据质量不高?蚂蚁金服数据资产管理平台了解一下
查看>>
我所理解的原型&原型链
查看>>
在项目中遇到的一些bug
查看>>
微服务前端开发框架React-Admin
查看>>
阿里云宣布 Serverless 容器服务 弹性容器实例 ECI 正式商业化
查看>>
让看不见的AI算法,助你拿下看得见的广阔市场
查看>>
polarphp:一个新的 PHP 语言运行时环境
查看>>
webpack的简单实例学习
查看>>
go-callvis 代码分析工具
查看>>
如何在网络视听行业建一扇内容安全大门?
查看>>
阿里云重磅推出物联网设备身份认证Link ID²
查看>>
手把手教你vue配置请求本地json数据
查看>>