乐趣区

关于spring:设计模式享元模式

享元模式

把现有的资源重复利用起来

Java 中常见的 OOm 有以下两种

  • 内存透露

    • 有意识的代码缺点,导致内存透露,JVM 不能取得间断的内存空 间。
  • 对象太多

    • 代码写得很烂,产生的对象太多,内存被耗尽。现没有内存透露,那只有一种起因

      • 代码太差把内存耗尽。
    • 有的对象咱们用完能够复用的,不必等到 oom

定义

  • 又称为轻量级模式,对象池的一种实现
  • 相似于线程池,线程池能够防止不停地创立和销毁多个对象,耗费性能
  • 提供了缩小对象数量而改善利用所需的对象构造的形式

共享细粒度对象,将多个对同一对象的拜访集中起来

结构型模式

生存中的例子

  • 中介

    • 房源共享的机制
  • 全国社保联网

公共类图

形象的享元角色Iflyweight

它简略地说就是一个产品的抽象类,同时定义出对象的内部状态和 外部状态的接口或实现。

具体的享元角色ConcreteFlyweight

​ 具体的一个产品类,实现形象角色定义的业务。该角色中须要留神 的是外部状态解决应该与环境无关,不应该呈现一个操作扭转了外部状 态,同时批改了内部状态,这是相对不容许的。

public class ConcreteFlyweight implements IFlyweight {
  private String intrinsicState;

  public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}


  public void operation(String extrinsicState) {System.out.println("Object address:" + System.identityHashCode(this));
    System.out.println("IntrinsicState:" + this.intrinsicState);
    System.out.println("ExtrinsicState:" + extrinsicState);
  }
}

FlyweightFactory 享元工厂

  • 职责非常简单,就是结构一个池容器,同时提供从池中取得对象的办法。
  • 享元模式的目标在于使用共享技术,使得一些细粒度的对象能够共 享,咱们的设计的确也应该这样,多应用细粒度的对象,便于重用或重构。
public class FlyweightFactory {private static Map<String, IFlyweight> pool = new HashMap<String, IFlyweight>();

  // 因为外部状态具备不变性,因而作为缓存的键
  public static IFlyweight getFlyweight(String intrinsicState) {if (!pool.containsKey(intrinsicState)) {IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
      pool.put(intrinsicState, flyweight);
    }
    return pool.get(intrinsicState);
  }
}

状态

外部状态

  • 外部状态是对象可共享进去的信息,存储在享元对象外部
  • 不会随环境扭转而扭转,它们能够作为 一个对象的动静附加信息,不用间接贮存在具体某个对象中,属于能够共享的局部。

内部状态

  • 对象能够被依赖的标记,不可共享的状态

    • Connection
    • 随外部环境变动而变动的属性
    • 例如连贯的沉闷状态,是否可用会随着变动而变动

利用场景

经常用于零碎底层开发,用来解决零碎的性能问题

零碎有大量类似对象,须要换冲池的场景

例子

连接池

在源码中的应用

String

会在内存中缓存了字符串

new String("lo") 创立了两个对象 Lo 先创立在了内存池,newString 创立了一个新的对象

  public static void main(String[] args) {
        String s1 = "hello";// "hello" 是编译器常量,String s1 是运行时,把常量地址赋值给他 <br>
        String s2 = "hello";
        String s3 = "he" + "llo"; // "he" + "llo" 两个常量相加,会在编译期解决 <br>
        String s4 = "hel" + new String("lo"); // "hel" "lo" new String 共建了 3 个空间,而后拼接起来是一个新的空间(why?)String s5 = new String("hello"); // s5 寄存的是堆中两头
        String s6 = s5.intern();  // 拿到常量中的地址,String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8; // 为什么这个不一样,因为是变量相加所以编译期没有做优化

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        System.out.println("s1" + System.identityHashCode(s1));
        System.out.println("s2" + System.identityHashCode(s2));
        System.out.println("s3" + System.identityHashCode(s3));
        System.out.println("s4" + System.identityHashCode(s4));
        System.out.println("s5" + System.identityHashCode(s5));
        System.out.println("s6" + System.identityHashCode(s6)); //s6 为 s5.intern()拿到的是常量池里的“hello”System.out.println("s7" + System.identityHashCode(s7));
        System.out.println("s8" + System.identityHashCode(s8));
        System.out.println("s9" + System.identityHashCode(s9));


        System.out.print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        System.out.println(s1==s2);//true
        System.out.println(s1==s3);//true
        System.out.println(s1==s4);//false
        System.out.println(s1==s9);//false
        System.out.println(s4==s5);//false
        System.out.println(s1==s6);//true



    }

s1 和 s2 存储在同一个内存地址

s1==s3 字面量 jdk 间接帮忙咱们放到一起做了优化解决

Integer

-128-127

public static void main(String[] args) {Integer a = Integer.valueOf(100);
  Integer b = 100;

  Integer c = Integer.valueOf(1000);
  Integer d = 1000;

  /**
    * 想查看 Integer 缓存的大小,debug 一下看一下 cache 的值 <br>
    */
  System.out.println("a==b:" + (a==b));
  System.out.println("c==d:" + (c==d));
}

能够看到在 Integer 外部保护了一个缓存,JVM 认为 -128-127 他们的应用频率十分高,所以做了一个对象缓存。

private static class IntegerCache {
  static final int low = -128;
  static final int high;
  static final Integer cache[];

  static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
      sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
      try {int i = parseInt(integerCacheHighPropValue);
        i = Math.max(i, 127);
        // Maximum array size is Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
      } catch(NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}
    }
    high = h;

    cache = new Integer[(high - low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
      cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
  }

  private IntegerCache() {}
}
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

Long

Long 同理

public static Long valueOf(long l) {
  final int offset = 128;
  if (l >= -128 && l <= 127) { // will cache
    return LongCache.cache[(int)l + offset];
  }
  return new Long(l);
}

-128-127

总结

优缺点

  • 长处

    • 缩小对象的创立,升高内存中对象的数量,升高零碎的内存,提高效率
    • 缩小内存之外的其余资源的占用
  • 毛病

    • 须要关注内外部状态
    • 线程平安问题
    • 暗藏了很多信息

      • 例如 Integer
    • 让零碎的程序和逻辑变得复杂

享元和代理的区别

  • 代理是关注与性能的加强
  • 享元不是为了性能减少而是为了防止反复创建对象

享元和单例的关系

  • 个别享元会配合单例一起应用

留神

  • 肯定须要留神线程平安问题,容器记得应用线程平安的容器

问题

什么时候应用享元模式呢

  1. 零碎中存在大量的类似对象

    1. 例如 String,Integer,Long 外部保护的缓存
  2. 须要缓冲池的场景

    1. 例如说线程池的保护

      1. 须要留神 ThreadLocal 如果用线程池,变量不记得回收可能呈现问题
    2. 连接池的保护
  3. 细粒度的对象都具备较靠近的内部状态,而且外部状态与环境无关,也就是说对象没有特定身份。

我的笔记仓库地址 gitee 快来给我点个 Star 吧

退出移动版