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

设计模式(三) 单例模式

作者头像
乐百川
发布2022-05-05 20:02:59
1760
发布2022-05-05 20:02:59
举报

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

单例模式有几个要点:

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

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

单例的实现

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

懒汉式(非线程安全)

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

代码语言:javascript
复制
public class UnThreadSafeSingleton {
    private UnThreadSafeSingleton() {
    }

    private static UnThreadSafeSingleton singleton;

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

懒汉式(同步的)

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

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

代码语言:javascript
复制
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会帮我们创建单例。这种方式的缺点就是加载类的时候就创建了单例对象,没有懒加载。

代码语言:javascript
复制
public class FirstLoadSingleton {
    private static FirstLoadSingleton singleton;

    private FirstLoadSingleton() {
    }

    static {
        singleton = new FirstLoadSingleton();
    }

    public static FirstLoadSingleton getSingleton() {
        return singleton;
    }
}

双检锁方式

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

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

代码语言:javascript
复制
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加载单例。

代码语言:javascript
复制
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的枚举还是感觉到比较陌生的话回去复习一下枚举类的用法。

代码语言:javascript
复制
public enum EnumSingleton {
    Instance;

    private String data;

    EnumSingleton() {
        //在构造方法中进行初始化
        data = "Some data";
    }

    public String getData() {
        return data;
    }
}

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例的实现
    • 懒汉式(非线程安全)
      • 懒汉式(同步的)
        • 饿汉式(静态初始化)
          • 双检锁方式
            • 静态内部类方式
              • 枚举方式
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档