设计模式-享元模式

享元模式

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

Java中常见的OOm有以下两种

  • 内存泄漏

    • 无意识的代码缺陷,导致内存泄漏,JVM不能获得连续的内存空 间。
  • 对象太多

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

      • 代码太差把内存耗尽。
    • 有的对象我们用完可以复用的,不用等到oom

定义

  • 又称为轻量级模式,对象池的一种实现
  • 类似于线程池,线程池可以避免不停地创建和销毁多个对象,消耗性能
  • 提供了减少对象数量而改善应用所需的对象结构的方式
共享细粒度对象,将多个对同一对象的访问集中起来

结构型模式

生活中的例子

  • 中介

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

公共类图

image-20210104190738715

抽象的享元角色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 pool = new HashMap();

  // 因为内部状态具备不变性,因此作为缓存的键
  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是运行时,把常量地址赋值给他
String s2 = "hello"; String s3 = "he" + "llo"; // "he" + "llo" 两个常量相加,会在编译期处理
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直接帮助我们放到一起做了优化处理

image-20210106114712186

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的值
*/ 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吧

你可能感兴趣的:(spring)