享元模式

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

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吧