zoukankan      html  css  js  c++  java
  • Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架、中间件和应用开发中。单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程、类的加载等知识,系统地介绍一下单例模式的演变,并体现在7种不同的单例设计中。说到这个,非常像孔乙己里那个“回字有四种写法”的梗,不过与封建迂腐文人不同的是,从简单的单例设计变化,可以看到一个需求演变的过程,看到一个方法不断完善的过程。

    传送门:Java并发编程中的设计模式解析(一)

    1. 饿汉式

    最简单的单例设计,优点是线程安全,但是因为类加载即初始化实例,加入实例变量比较多的话,会占用较多的内存。

     1 //不允许被继承
     2 public final class SingletonStarve {
     3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
     4     @SuppressWarnings("unused")
     5     private byte[] data = new byte[1024];
     6     //定义静态实例对象的时候直接初始化
     7     private static SingletonStarve instance = new SingletonStarve();
     8     //私有化构造函数, 不允许直接new对象
     9     private SingletonStarve() {}
    10     //提供公共的方法获取实例对象
    11     public static SingletonStarve getInstance() {
    12         return instance;
    13     }
    14 }

    2. 懒汉式

    实现了单例设计的懒加载,节省了前期内存空间的占用,但是在多线程环境下可能会导致多对象的产生,破坏实例唯一性。

     1 //不允许被继承
     2 public final class LazySingleton {
     3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
     4     @SuppressWarnings("unused")
     5     private byte[] data = new byte[1024];
     6     //定义静态实例对象, 不直接初始化
     7     private static LazySingleton instance = null;
     8     //私有化构造函数, 不允许直接new对象
     9     private LazySingleton() {}
    10     //提供公共的方法获取实例对象
    11     public static LazySingleton getInstance() {
    12         if(null == instance) {
    13             instance = new LazySingleton();
    14         }
    15         return instance;
    16     }
    17 }

    3. 懒汉式+同步锁

    通过使用synchronized关键字使getInstance方法变为同步方法,从而确保线程安全,但带来了一定的性能问题。

     1 //不允许被继承
     2 public final class SyncLazySingleton {
     3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
     4     @SuppressWarnings("unused")
     5     private byte[] data = new byte[1024];
     6     //定义静态实例对象, 不直接初始化
     7     private static SyncLazySingleton instance = null;
     8     //私有化构造函数, 不允许直接new对象
     9     private SyncLazySingleton() {}
    10     //提供公共的方法获取实例对象, 通过synchronized修饰为同步方法
    11     public static synchronized SyncLazySingleton getInstance() {
    12         if(null == instance) {
    13             instance = new SyncLazySingleton();
    14         }
    15         return instance;
    16     }
    17 }

    4. Double-Check

    推荐使用:Double-Check单例模式,通过两次非空判断,并且对第二次判断加锁,确保了多线程下的单例设计安全,同时保证了性能。

    注意:Double-check有可能因为JVM指令重排的原因,导致空指针异常;使用volatile修饰对象引用,可以确保其可见性,避免异常

     1 //不允许被继承
     2 public final class VolatileDoubleCheckSingleton {
     3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
     4     @SuppressWarnings("unused")
     5     private byte[] data = new byte[1024];
     6     //定义静态实例对象, 不直接初始化
     7     //通过volatile, 避免指令重排序导致的空指针异常
     8     private static volatile VolatileDoubleCheckSingleton instance = null;
     9     Connection conn;
    10     Socket socket;
    11     //私有化构造函数, 不允许直接new对象
    12     //由于指令重排序, 实例化顺序可能重排, 从而导致空指针,使用volatile关键字修饰单例解决
    13     private VolatileDoubleCheckSingleton() {
    14         //this.conn;
    15         //this.socket;
    16     }
    17     //提供公共的方法获取实例对象
    18     public static VolatileDoubleCheckSingleton getInstance() {
    19      20         if(null == instance) {
    21             synchronized(VolatileDoubleCheckSingleton.class) {
    22                 if(null == instance) {//以下赋值因为不是原子性的,如果不使用volatile使instance在多个线程中可见,将可能导致空指针
    23                     instance = new VolatileDoubleCheckSingleton();                    
    24                 }
    25             }
    26         }
    27         return instance;
    28     }
    29 }

    5. 静态内部类

    推荐使用:通过使用静态内部类,巧妙地避免了线程不安全,并且节省了前期内存空间,编码非常简洁。

     1 //不允许被继承
     2 public final class HolderSingleton {
     3     //实例变量
     4     @SuppressWarnings("unused")
     5     private byte[] data = new byte[1024];
     6     //私有化构造器
     7     private HolderSingleton() {}
     8     //定义静态内部类Holder, 及内部实例成员, 并直接初始化
     9     private static class Holder{
    10         private static HolderSingleton instance = new HolderSingleton();
    11     }
    12     //通过Holder.instance获得单例
    13     public static HolderSingleton getInstance() {
    14         return Holder.instance;
    15     }
    16 }

     

    6. 枚举类

    《Effective Java》中推荐的单例设计模式,缺点是饿汉式,并且对编码能力要求较高。

     1 //枚举本身是final的, 不允许被继承
     2 public enum EnumSingleton {
     3     INSTANCE;
     4     //实例变量
     5     @SuppressWarnings("unused")
     6     private byte[] data = new byte[1024];
     7     
     8     EnumSingleton() {
     9         System.out.println("INSTANCE will be initialized immediately");
    10     }
    11     public static void method() {
    12         //调用该方法会主动使用EnumSingleton, INSTANCE将会实例化
    13     }
    14     public static EnumSingleton getInstance() {
    15         return INSTANCE;
    16     }
    17 }

    7. 内部枚举类

     1 /*
     2  * 使用枚举类作为内部类实现懒加载
     3  */
     4 public final class LazyEnumSingleton {
     5     private LazyEnumSingleton(){}
     6     private enum EnumHolder{
     7         INSTANCE;
     8         private LazyEnumSingleton instance;
     9         EnumHolder(){
    10             this.instance = new LazyEnumSingleton();
    11         }
    12         private LazyEnumSingleton getLazyEnumSingleton() {
    13             return instance;
    14         }
    15     }
    16     public static LazyEnumSingleton getInstance() {
    17         return EnumHolder.INSTANCE.getLazyEnumSingleton();
    18     }
    19 }
  • 相关阅读:
    思维发散的双刃剑
    一个请求过来都经过了什么?(Thrift版)
    业务开发转基础开发,这三种「高可用」架构你会么?
    程序常用的设计技巧
    美团分布式服务通信框架及服务治理系统OCTO
    《程序员修炼之道》解读
    面试官说:你真的不是不优秀只是不合适
    架构视角-文件的通用存储原理
    那些影响深远的弯路
    iOS sqlite ORM框架-LKDBHelper
  • 原文地址:https://www.cnblogs.com/leoliu168/p/9932374.html
Copyright © 2011-2022 走看看