lizema lizema
首页
  • js

    • js
  • Git相关

    • 《Git》
  • 设计模式

    • 设计模式
  • java
  • jdk
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • HTML
  • CSS
  • 学习方法
  • 敏捷开发心得
  • 心情杂货
  • 实用技巧
  • GPT相关
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

malize

各自努力,顶峰相见。
首页
  • js

    • js
  • Git相关

    • 《Git》
  • 设计模式

    • 设计模式
  • java
  • jdk
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • HTML
  • CSS
  • 学习方法
  • 敏捷开发心得
  • 心情杂货
  • 实用技巧
  • GPT相关
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 工作中用到的设计模式
  • 享元模式
  • 单例模式
    • 原型模式
    • 备忘录模式
    • 外观模式
    • 工厂方法模式
    • 模板方法模式
    • 策略模式
    • 装饰模式
    • 访问者模式
    • 责任链模式
    • 适配器模式
    • 中介者模式
    • 设计模式
    malize
    2020-11-18
    目录

    单例模式

    # 单例模式

    # 一、单例模式

    1. 原理:单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

    2. 优缺点

      1. 1 优点
      • 唯一实例:确保一个类只有一个实例,节省了系统资源,尤其是在需要频繁创建和销毁实例的情况下。
      • 全局访问:通过全局访问点提供对唯一实例的访问,简化了对共享资源的管理。
      • 延迟实例化:可以实现延迟实例化,即在首次使用时才创建实例,优化了系统资源的使用。
      • 灵活性:可以通过继承和多态性来扩展单例模式,使得单例类具有更大的灵活性。
      • 线程安全:通过适当的实现方法(如双重检查锁定、静态内部类)可以确保单例模式在多线程环境下的安全性。
      1. 2 缺点
      • 全局状态:单例模式引入了全局状态,可能导致系统的难以测试和调试,增加了系统的复杂性。
      • 并发问题:在多线程环境下,如果实现不当,可能会导致线程安全问题。
      • 隐藏依赖关系:单例模式使得类之间的依赖关系不明显,可能导致代码的可读性和可维护性下降。
      • 难以扩展:单例模式的类很难被子类化,因为构造函数是私有的,限制了扩展性。
      • 生命周期不明确:单例实例的生命周期与应用程序一致,可能导致资源无法及时释放,从而引起内存泄漏等问题。
    3. 适用场景

      • 配置管理:系统的配置文件读取和管理通常需要全局唯一的实例。
      • 日志管理:全局共享的日志管理器确保所有模块都能记录日志。
      • 连接池:数据库连接池、线程池等资源池需要全局唯一实例进行管理。
      • 缓存:系统缓存通常需要全局唯一实例进行管理,以确保数据的一致性。
    4. 实现方式

      • 饿汉式:类加载时就创建实例,简单但在某些情况下会浪费资源。
      • 懒汉式:首次使用时才创建实例,但需要注意线程安全问题。
      • 双重检查锁定:懒汉式的改进版本,通过双重检查和同步块保证线程安全。
      • 静态内部类:利用类加载机制,既实现了延迟加载又保证了线程安全。
      • 枚举:使用枚举来实现单例,天生保证线程安全和防止反序列化破坏单例。

    # 二、单例模式实现代码

    1. 懒汉模式(线程不安全)

      public class Singleton_01 {
      
          private static Singleton_01 instance;
      
          private Singleton_01() {
          }
      
          public static Singleton_01 getInstance(){
              if (null != instance) return instance;
              instance = new Singleton_01();
              return instance;
          }
      
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
    2. 懒汉模式(线程安全)

      public class Singleton_02 {
      
          private static Singleton_02 instance;
      
          private Singleton_02() {
          }
      
          public static synchronized Singleton_02 getInstance(){
              if (null != instance) return instance;
              instance = new Singleton_02();
              return instance;
          }
      
      }
       
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      • 该方法会导致性能开销。在高并发环境下,每次获取实例时都需要进行同步,可能会显著降低系统性能。
    3. 饿汉模式(线程安全)

      public class Singleton_03 {
      
          private static Singleton_03 instance = new Singleton_03();
      
          private Singleton_03() {
          }
      
          public static Singleton_03 getInstance() {
              return instance;
          }
      
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
    4. 使用内部类(线程安全)

      public class Singleton_04 {
      
          private static class SingletonHolder {
              private static Singleton_04 instance = new Singleton_04();
          }
      
          private Singleton_04() {
          }
      
          public static Singleton_04 getInstance() {
              return SingletonHolder.instance;
          }
      
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      • 实例在首次调用 getInstance 方法时才会创建。这是通过静态内部类实现的,因为静态内部类不会在其外部类加载时就加载,只有在被引用时才会加载和初始化。
    5. 双重锁校验(线程安全)

      public class Singleton_05 {
      
          private static volatile Singleton_05 instance;
      
          private Singleton_05() {
          }
      
          public static Singleton_05 getInstance(){
             if(null != instance) return instance;
             synchronized (Singleton_05.class){
                 if (null == instance){
                     instance = new Singleton_05();
                 }
             }
             return instance;
          }
      
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      • 为什么要两次判断instance:假如instance没有被实例化,两个进程通过第一个判断,由于”锁“机制,只能由一个线程进入,另一个才能进入。如果没有第二层判断,则第一个线程创建实例后,第二个线程仍然能创建线程,不符合单例。
    6. CAS「AtomicReference」(线程安全)

    public class Singleton_06 {
    
        private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();
    
        private Singleton_06() {
        }
    
        public static final Singleton_06 getInstance() {
            for (; ; ) {
                Singleton_06 instance = INSTANCE.get();
                if (null != instance) return instance;
                INSTANCE.compareAndSet(null, new Singleton_06());
                return INSTANCE.get();
            }
        }
    
        public static void main(String[] args) {
            System.out.println(Singleton_06.getInstance());
            System.out.println(Singleton_06.getInstance());
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    • 使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
    • CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。
    • 这种使用 AtomicReference 的方式是一种线程安全且延迟加载的单例实现,适用于希望避免显式同步并利用 CAS 提高并发性能的场景。但由于实现相对复杂,在实际应用中需要权衡使用场景和代码可维护性。对于大多数情况,使用静态内部类或双重检查锁定(DCL)会是更简单和直观的选择。
    1. 枚举单例(线程安全)

      public enum Singleton {
          INSTANCE;
      
          public void someMethod() {
              // 单例类中的方法
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      public class Main {
          public static void main(String[] args) {
              Singleton singleton = Singleton.INSTANCE;
              singleton.someMethod();
          }
      }
      
      1
      2
      3
      4
      5
      6
      • 实现分析:

        1. 线程安全:枚举类型的线程安全由 Java 虚拟机保证,JVM 确保枚举类型在使用时会被正确地加载和初始化。

        2. 防止反射攻击:反射攻击是通过反射机制绕过访问控制进行对象创建或方法调用。枚举类型在 Java 中是一个特殊的类,它的构造方法在字节码层面是 private 的,且在任何尝试通过反射调用时会抛出 java.lang.NoSuchMethodException 或 java.lang.IllegalArgumentException,防止了通过反射创建实例。

        3. 序列化:在 Java 的序列化机制中,枚举类型有着与普通类不同的序列化和反序列化逻辑。在反序列化过程中,JVM 会通过 Enum.valueOf 方法查找已存在的枚举实例,而不会新建对象,这样就防止了反序列化破坏单例。

    # 三、优化后

    1. 工厂结构
    itstack-demo-design-1-02
    └── src
        ├── main
        │   └── java
        │       └── org.itstack.demo.design
        │           ├── store    
        │           │   ├── impl
        │           │   │   ├── CardCommodityService.java
        │           │   │   ├── CouponCommodityService.java 
        │           │   │   └── GoodsCommodityService.java  
        │           │   └── ICommodity.java
        │           └── StoreFactory.java 
        └── test
             └── java
                 └── org.itstack.demo.design.test
                     └── ApiTest.java
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    1. UML图

    2. 发奖接口

    public interface ICommodity {
    
        void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
    
    }
    
    1
    2
    3
    4
    5
    1. 实现奖品发放接口(发奖工厂)

      优惠劵

    public class CouponCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);
    
        private CouponService couponService = new CouponService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
            logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
            if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    ​ 事物商品

    public class GoodsCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);
    
        private GoodsService goodsService = new GoodsService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            DeliverReq deliverReq = new DeliverReq();
            deliverReq.setUserName(queryUserName(uId));
            deliverReq.setUserPhone(queryUserPhoneNumber(uId));
            deliverReq.setSku(commodityId);
            deliverReq.setOrderId(bizId);
            deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
            deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
            deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));
    
            Boolean isSuccess = goodsService.deliverGoods(deliverReq);
    
            logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[优惠券]:{}", isSuccess);
    
            if (!isSuccess) throw new RuntimeException("实物商品发放失败");
        }
    
        private String queryUserName(String uId) {
            return "花花";
        }
    
        private String queryUserPhoneNumber(String uId) {
            return "15200101232";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    ​ 第三方兑换卡

    public class CardCommodityService implements ICommodity {
    
        private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);
    
        // 模拟注入
        private IQiYiCardService iQiYiCardService = new IQiYiCardService();
    
        public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
            String mobile = queryUserMobile(uId);
            iQiYiCardService.grantToken(mobile, bizId);
            logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
            logger.info("测试结果[爱奇艺兑换卡]:success");
        }
    
        private String queryUserMobile(String uId) {
            return "15200101232";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    1. 创建商店工厂
    public class StoreFactory {
    
        public ICommodity getCommodityService(Integer commodityType) {
            if (null == commodityType) return null;
            if (1 == commodityType) return new CouponCommodityService();
            if (2 == commodityType) return new GoodsCommodityService();
            if (3 == commodityType) return new CardCommodityService();
            throw new RuntimeException("不存在的商品服务类型");
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1. 测试验证
    @Test
    public void test_commodity() throws Exception {
        StoreFactory storeFactory = new StoreFactory();
        // 1. 优惠券
        ICommodity commodityService_1 = storeFactory.getCommodityService(1);
        commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
        // 2. 实物商品
        ICommodity commodityService_2 = storeFactory.getCommodityService(2);
        
        Map<String,String> extMap = new HashMap<String,String>();
        extMap.put("consigneeUserName", "谢飞机");
        extMap.put("consigneeUserPhone", "15200292123");
        extMap.put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");
    
        commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap);
        // 3. 第三方兑换卡(爱奇艺)
        ICommodity commodityService_3 = storeFactory.getCommodityService(3);
        commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);
    }
        
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    ​ 结果:

    模拟发放优惠券一张:10001,EGM1023938910232121323432,791098764902132
    22:48:10.922 [main] INFO  o.i.d.d.s.i.CouponCommodityService - 请求参数[优惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
    22:48:10.957 [main] INFO  o.i.d.d.s.i.CouponCommodityService - 测试结果[优惠券]:{"code":"0000","info":"发放成功"}
    模拟发货实物商品一个:{"consigneeUserAddress":"吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109","consigneeUserName":"谢飞机","consigneeUserPhone":"15200292123","orderId":"1023000020112221113","sku":"9820198721311","userName":"花花","userPhone":"15200101232"}
    22:48:10.962 [main] INFO  o.i.d.d.s.impl.GoodsCommodityService - 请求参数[优惠券] => uId:10001 commodityId:9820198721311 bizId:1023000020112221113 extMap:{"consigneeUserName":"谢飞机","consigneeUserAddress":"吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109","consigneeUserPhone":"15200292123"}
    22:48:10.962 [main] INFO  o.i.d.d.s.impl.GoodsCommodityService - 测试结果[优惠券]:true
    模拟发放爱奇艺会员卡一张:15200101232,null
    22:48:10.963 [main] INFO  o.i.d.d.s.impl.CardCommodityService - 请求参数[爱奇艺兑换卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null
    22:48:10.963 [main] INFO  o.i.d.d.s.impl.CardCommodityService - 测试结果[爱奇艺兑换卡]:success
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 四、实现方式

    1. 让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。
    2. 在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。
    3. 在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。
    4. 现在, 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。
    5. 如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。

    结合案例理解

    1. 源代码产品是GoodsService、CouponService、IQiYiCardService(可以加个接口Service声明对这些产品都有意义的方法)
    2. 工厂接口ICommodity,子工厂CouponCommodityService、GoodsCommodityService、CardCommodityService
    3. 各个子工厂实现对各自产品的操作
    4. 操作工厂getCommodityService(Integer commodityType),根据commodityType获取你想要的工厂
    编辑 (opens new window)
    #重学Java设计模式
    享元模式
    原型模式

    ← 享元模式 原型模式→

    最近更新
    01
    其他
    02
    其他
    03
    名人总结
    08-27
    更多文章>
    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式