享元模式在JDK源码中的应用——Java设计模式系列学习笔记

Posted 来老铁干了这碗代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了享元模式在JDK源码中的应用——Java设计模式系列学习笔记相关的知识,希望对你有一定的参考价值。

1. String中的享元模式

Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。

我们做一个测试:

public class Main 
    public static void main(String[] args) 
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "he" + "llo";
        String s4 = "hel" + new String("lo");
        String s5 = new String("hello");
        String s6 = s5.intern();
        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        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
    

String类的final修饰的,以字面量的形式创建String变量时,jvm会在编译期间就把该字面量hello放到字符串常量池中,由Java程序启动的时候就已经加载到内存中了。这个字符串常量池的特点就是有且只有一份相同的字面量,如果有其它相同的字面量,jvm则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用。

由于s2指向的字面量hello在常量池中已经存在了(s1先于s2),于是jvm就返回这个字面量绑定的引用,所以s1==s2

s3中字面量的拼接其实就是hello,jvm在编译期间就已经对它进行优化,所以s1和s3也是相等的。

s4中的new String("lo")生成了两个对象,helnew String("lo")hel存在字符串常量池,new String("lo")存在堆中,String s4 = "hel" + new String("lo")实质上是两个对象的相加,编译器不会进行优化,相加的结果存在堆中,而s1存在字符串常量池中,当然不相等。s1==s9的原理一样。

s4==s5两个相加的结果都在堆中,不用说,肯定不相等。

s1==s6中,s5.intern()方法能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池中(字符串常量池的内容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用,否则,创建复制一份该字面量到字符串常量池并返回它的引用。因此s1==s6输出true

2. Integer 中的享元模式

使用例子如下:

public static void main(String[] args) 
    Integer i1 = 12 ;
    Integer i2 = 12 ;
    System.out.println(i1 == i2);

    Integer b1 = 128 ;
    Integer b2 = 128 ;
    System.out.println(b1 == b2);

输出:

true
false

为什么第一个是true,第二个是false
反编译后可以发现 Integer b1 = 128; 实际变成了 Integer b1 = Integer.valueOf(128);,所以我们来看 Integer 中的 valueOf 方法的实现

public final class Integer extends Number implements Comparable<Integer> 
    public static Integer valueOf(int var0) 
        return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
    
    //...省略...

IntegerCache 缓存类

//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
private static class IntegerCache 
    static final int low = -128;//区间的最低值
    static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
    static final Integer cache[]; //缓存数组

    static 
        // high value may be configured by property
        int h = 127;
        //这里可以在运行时设置虚拟机参数来确定h  :-Djava.lang.Integer.IntegerCache.high=250
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) //用户设置了
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);//虽然设置了但是还是不能小于127
            // 也不能超过最大值
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        //循环将区间的数赋值给cache[]数组
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    

    private IntegerCache() 

可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象

public final class Long extends Number implements Comparable<Long> 
    public static Long valueOf(long var0) 
        return var0 >= -128L && var0 <= 127L ? Long.LongCache.cache[(int)var0 + 128] : new Long(var0);
       
    private static class LongCache 
        private LongCache()

        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static 
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        
    
    //...

同理,Long 中也有缓存,不过不能指定缓存最大值

3. Apache Commons Pool2中的享元模式

对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)

Apache Commons Pool实现了对象池的功能。定义了对象的生成、销毁、激活、钝化等操作及其状态转换,并提供几个默认的对象池实现。

有几个重要的对象:

  • PooledObject(池对象):用于封装对象(如:线程、数据库连接、TCP连接),将其包裹成可被池管理的对象。
  • PooledObjectFactory(池对象工厂):定义了操作PooledObject实例生命周期的一些方法,PooledObjectFactory必须实现线程安全。
  • Object Pool (对象池):Object Pool负责管理PooledObject,如:借出对象,返回对象,校验对象,有多少激活对象,有多少空闲对象。
 // 对象池
 private final Map<S, PooledObject<S>> allObjects = new ConcurrentHashMap<S, PooledObject<S>>();

重要方法:

borrowObject:从池中借出一个对象。
returnObject:将一个对象返还给池。

以上是关于享元模式在JDK源码中的应用——Java设计模式系列学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之享元模式与组合模式详解和应用

Java描述设计模式(18):享元模式

《Android源码设计模式》--享元模式

设计模式-享元模式

深入理解设计模式-享元模式

深入理解设计模式-享元模式