{"id":21909490,"url":"https://github.com/jsbintask22/design-pattern-learning","last_synced_at":"2025-03-22T07:50:04.985Z","repository":{"id":39919510,"uuid":"168119692","full_name":"jsbintask22/design-pattern-learning","owner":"jsbintask22","description":"各种设计模式学习，demo","archived":false,"fork":false,"pushed_at":"2024-05-16T17:21:50.000Z","size":45,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-01-27T08:12:30.190Z","etag":null,"topics":["design-patterns","design-patterns-implemented-in-java","java"],"latest_commit_sha":null,"homepage":"https://jsbintask.cn/2019/01/29/designpattern/singleton/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsbintask22.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-29T08:37:28.000Z","updated_at":"2022-02-27T05:46:44.000Z","dependencies_parsed_at":"2022-09-21T05:06:02.106Z","dependency_job_id":null,"html_url":"https://github.com/jsbintask22/design-pattern-learning","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbintask22%2Fdesign-pattern-learning","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbintask22%2Fdesign-pattern-learning/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbintask22%2Fdesign-pattern-learning/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsbintask22%2Fdesign-pattern-learning/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsbintask22","download_url":"https://codeload.github.com/jsbintask22/design-pattern-learning/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244924766,"owners_count":20532874,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-patterns","design-patterns-implemented-in-java","java"],"created_at":"2024-11-28T17:18:42.455Z","updated_at":"2025-03-22T07:50:04.965Z","avatar_url":"https://github.com/jsbintask22.png","language":"Java","readme":"## 什么是单例？为什么要用单例？\n一个类被设计出来，就代表它表示具有某种行为（方法），属性（成员变量），而一般情况下，当我们想使用这个类时，会使用**new**关键字，这时候jvm会帮我们构造一个该类的实例。而我们知道，对于**new**这个关键字以及该实例，相对而言是比较耗费资源的。所以如果我们能够想办法在jvm启动时就**new**好，或者在某一次实例**new**好以后，以后不再需要这样的动作，就能够节省很多资源了。\n\n## 哪些类可以使用单例？\n一般而言，我们总是希望**无状态**的类能够设计成单例，那这个**无状态**代表什么呢？ 简单而言，对于同一个实例，如果多个线程同时使用，并且不使用额外的线程同步手段，不会出现线程同步的问题，我们就可以认为是**无状态**的，再简单点：一个类没有成员变量，或者它的成员变量也是**无状态**的，我们就可以考虑设计成单例。\n\n## 实现方法\n好了，我们已经知道什么是单例，为什么要使用单例了，那我们接下来继续讨论下怎么实现单例。\n一般来说，我们可以把单例分为**行为上的单例**和**管理上的单例**。**行为上的单例**代表不管如何操作（此处不谈cloneable，反射），至始至终jvm中都只有一个类的实例，而**管理上的单例**则可以理解为：不管谁去使用这个类，都要守一定的**规矩**，比方说，我们使用某个类，只能从指定的地方’去拿‘，这样拿到就是同一个类了。\n而对于**管理上的单例**，相信大家最为熟悉的就是spring了，spring将所有的类放到一个**容器**中，以后使用该类都从该**容器**去取，这样就保证了单例。\n所以这里我们剩下的就是接着来谈谈如何实现**行为上的单例**了。一般来说，这种单例实现有两种思路，**私有构造器，枚举**。\n\n### 枚举实现单例\n枚举实现单例是最为推荐的一种方法，因为就算通过序列化，反射等也没办法破坏单例性，例子：\n```java\npublic enum SingletonEnum {\n    INSTANCE;\n\n    public static void main(String[] args) {\n        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);\n    }\n}\n```\n结果自然是**true**，而如果我们尝试使用反射破坏单例性：\n```java\npublic enum BadSingletonEnum {\n    /**\n     *\n     */\n    INSTANCE;\n\n    public static void main(String[] args) throws Exception{\n        System.out.println(BadSingletonEnum.INSTANCE == BadSingletonEnum.INSTANCE);\n\n        Constructor\u003cBadSingletonEnum\u003e badSingletonEnumConstructor = BadSingletonEnum.class.getDeclaredConstructor();\n        badSingletonEnumConstructor.setAccessible(true);\n        BadSingletonEnum badSingletonEnum = badSingletonEnumConstructor.newInstance();\n\n        System.out.println(BadSingletonEnum.INSTANCE == badSingletonEnum);\n    }\n}\n```\n结果如下：\n```java\nException in thread \"main\" java.lang.NoSuchMethodException: cn.jsbintask.BadSingletonEnum.\u003cinit\u003e()\n\tat java.lang.Class.getConstructor0(Class.java:3082)\n\tat java.lang.Class.getDeclaredConstructor(Class.java:2178)\n\tat cn.jsbintask.BadSingletonEnum.main(BadSingletonEnum.java:18)\n```\n异常居然是没有**init**方法，这是为什么呢？ 那我们反编译查看下这个枚举类的字节码：\n```java\n// class version 52.0 (52)\n// access flags 0x4031\n// signature Ljava/lang/Enum\u003cLcn/jsbintask/BadSingletonEnum;\u003e;\n// declaration: cn/jsbintask/BadSingletonEnum extends java.lang.Enum\u003ccn.jsbintask.BadSingletonEnum\u003e\npublic final enum cn/jsbintask/BadSingletonEnum extends java/lang/Enum {\n\n  // compiled from: BadSingletonEnum.java\n\n  // access flags 0x4019\n  public final static enum Lcn/jsbintask/BadSingletonEnum; INSTANCE\n\n  // access flags 0x101A\n  private final static synthetic [Lcn/jsbintask/BadSingletonEnum; $VALUES\n}\n```\n结果发现这个枚举类继承了抽象类**java.lang.Enum**，我们接着看下**Enum**，发现构造器：\n```java\n/**\n    * Sole constructor.  Programmers cannot invoke this constructor.\n    * It is for use by code emitted by the compiler in response to\n    * enum type declarations.\n    *\n    * @param name - The name of this enum constant, which is the identifier\n    *               used to declare it.\n    * @param ordinal - The ordinal of this enumeration constant (its position\n    *         in the enum declaration, where the initial constant is assigned\n    *         an ordinal of zero).\n*/\nprotected Enum(String name, int ordinal) {\n    this.name = name;\n    this.ordinal = ordinal;\n}\n```\n那我们接着改变代码，反射调用这个构造器：\n```java\npublic enum BadSingletonEnum {\n    /**\n     *\n     */\n    INSTANCE();\n\n    public static void main(String[] args) throws Exception{\n        System.out.println(BadSingletonEnum.INSTANCE == BadSingletonEnum.INSTANCE);\n\n        Constructor\u003cBadSingletonEnum\u003e badSingletonEnumConstructor = BadSingletonEnum.class.getDeclaredConstructor(String.class, int.class);\n        badSingletonEnumConstructor.setAccessible(true);\n        BadSingletonEnum badSingletonEnum = badSingletonEnumConstructor.newInstance(\"test\", 0);\n\n        System.out.println(BadSingletonEnum.INSTANCE == badSingletonEnum);\n    }\n}\n```\n结果如下：\n```java\nException in thread \"main\" java.lang.IllegalArgumentException: Cannot reflectively create enum objects\n\tat java.lang.reflect.Constructor.newInstance(Constructor.java:417)\n\tat cn.jsbintask.BadSingletonEnum.main(BadSingletonEnum.java:21)\n```\n这次虽然方法找到了，但是直接给我们了一句**Cannot reflectively create enum objects**，不能够反射创造枚举对象，接着我们继续看下**newInstance（...）**这个方法：\n```java\npublic T newInstance(Object ... initargs)\n        throws InstantiationException, IllegalAccessException,\n               IllegalArgumentException, InvocationTargetException\n    {\n        if (!override) {\n            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {\n                Class\u003c?\u003e caller = Reflection.getCallerClass();\n                checkAccess(caller, clazz, null, modifiers);\n            }\n        }\n        if ((clazz.getModifiers() \u0026 Modifier.ENUM) != 0)\n            throw new IllegalArgumentException(\"Cannot reflectively create enum objects\");\n        ConstructorAccessor ca = constructorAccessor;   // read volatile\n        if (ca == null) {\n            ca = acquireConstructorAccessor();\n        }\n        @SuppressWarnings(\"unchecked\")\n        T inst = (T) ca.newInstance(initargs);\n        return inst;\n    }\n```\n关键代码就是：**if ((clazz.getModifiers() \u0026 Modifier.ENUM) != 0) throw new IllegalArgumentException(\"Cannot reflectively create enum objects\");**，所以就是jdk从根本上拒绝了使用反射去创建（知道为啥java推荐使用enum实现单例了吧），另外，我们再观察下**Enum**类的clone和序列化方法，如下：\n```java\nprotected final Object clone() throws CloneNotSupportedException {\n    throw new CloneNotSupportedException();\n}\n\nprivate void readObject(ObjectInputStream in) throws IOException,\n    ClassNotFoundException {\n    throw new InvalidObjectException(\"can't deserialize enum\");\n}\n\nprivate void readObjectNoData() throws ObjectStreamException {\n    throw new InvalidObjectException(\"can't deserialize enum\");\n}\n```\n一眼看出，直接丢出异常，**不允许这么做！（真亲儿子系列）**。\n所以，结论就是：枚举是最靠谱的实现单例的方式！\n\n### 私有构造器\n另外一个实现单例最普通的方法则是**私有构造器，开放获取实例公共方法**，虽然这种方法还是可以用clone，序列化，反射破坏单例性（除非特殊情况，我们不会这么做），但是却是最容易理解使用的。而这种方式又分了**饱汉式**，**饿汉式**。\n\n#### 饿汉式\n看名字就知道，饥渴！（咳咳，开个玩笑），它指的是当一个类被jvm加载的时候就会被实例化，这样可以从根本上解决多个线程的同步问题，例子如下：\n```java\npublic class FullSingleton {\n    private static FullSingleton ourInstance = new FullSingleton();\n\n    public static FullSingleton getInstance() {\n        return ourInstance;\n    }\n\n    private FullSingleton() {\n    }\n\n    public static void main(String[] args) {\n        System.out.println(FullSingleton.getInstance() == FullSingleton.getInstance());\n    }\n}\n```\n结果自然是**true**，虽然这种做法很方便的帮我们解决了多线程实例化的问题，但是缺点也很明显，因为这句代码**private static FullSingleton ourInstance = new FullSingleton();**的关系，所以该类一旦被jvm加载就会马上实例化，那如果我们不想用这个类怎么办呢？ 是不是就浪费了呢？既然这样，我们来看下替代方案！ 饱汉式。\n\n#### 饱汉式\n既然是**饱**，就代表它不着急，那我们可以这么写：\n```java\npublic class HungryUnsafeSingleton {\n    private static HungryUnsafeSingleton instance;\n    \n    public static HungryUnsafeSingleton getInstance() {\n        if (instance == null) {\n            instance = new HungryUnsafeSingleton();\n        }\n        \n        return instance;\n    }\n    \n    private HungryUnsafeSingleton() {}\n}\n```\n用意很容易理解，就是用到**getInstance（）**方法才去检查instance，如果为null，就new一个，这样就不怕浪费了，但是这个时候问题就来了：现在有这么一种情况，在有两个线程同时 运行到了  **instane == null**这个语句，并且都通过了，那他们就会都实例化一个对象，这样就又不是单例了。既然这样，哪有什么解决办法呢？ **锁方法**\n1. 直接同步方法\n这种方法比较干脆利落，那就是直接在getInstance（）方法上加锁，这样就解决了线程问题：\n```java\npublic class HungrySafeSingleton {\n    private static HungrySafeSingleton instance;\n\n    public static synchronized HungrySafeSingleton getInstance() {\n        if (instance == null) {\n            instance = new HungrySafeSingleton();\n        }\n\n        return instance;\n    }\n\n    private HungrySafeSingleton() {\n        System.out.println(\"HungryUnsafeSingleton.HungryUnsafeSingleton\");\n    }\n\n    public static void main(String[] args) {\n        System.out.println(HungrySafeSingleton.getInstance() == HungrySafeSingleton.getInstance());\n    }\n}\n```\n很简单，很容易理解，加锁，只有一个线程能实例该对象。但是，此时问题又来了，我们知道对于静态方法而言，synchronized关键字会锁住整个 Class，这时候又会有性能问题了（尼玛墨迹），那有没有优化的办法呢？ **双重检查锁**：\n```java\npublic class HungrySafeSingleton {\n    private static volatile HungrySafeSingleton instance;\n\n    public static HungrySafeSingleton getInstance() {\n        /* 使用一个本地变量可以提高性能 */\n        HungrySafeSingleton result = instance;\n\n        if (result == null) {\n\n            synchronized (HungrySafeSingleton.class) {\n\n                result = instance;\n                if (result == null) {\n                    instance = result = new HungrySafeSingleton();\n                }\n            }\n        }\n\n        return result;\n    }\n\n    private HungrySafeSingleton() {\n        System.out.println(\"HungryUnsafeSingleton.HungryUnsafeSingleton\");\n    }\n\n    public static void main(String[] args) {\n        System.out.println(HungrySafeSingleton.getInstance() == HungrySafeSingleton.getInstance());\n    }\n}\n```\n用意也很明显，synchronized关键字只加在了关键的地方，并且通过本地变量提高了性能（effective java），这样线程安全并且不浪费资源的单例就完成了。\n\n## 总结\n本章，我们一步一步从什么是单例，到为什么要使用单例，再到怎么使用单例，并且从源码角度分析了为什么枚举是最适合的实现方式，然后接着讲解了饱汉式，饿汉式的写法以及好处，缺点。","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsbintask22%2Fdesign-pattern-learning","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsbintask22%2Fdesign-pattern-learning","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsbintask22%2Fdesign-pattern-learning/lists"}