1故事背景一个程序员因为在生产环境改了一个方法参数,把int类型改成了Integer类型。因为涉及的钱,公司上线后损失惨重,程序员也被解雇了。信不信由你,请继续阅读。先看一段代码:publicstaticvoidmain(String[]args){Integera=Integer.valueOf(100);整数b=100;整数c=Integer.valueOf(129);整数d=129;System.out.println("a==b:"+(a==b));System.out.println("c==d:"+(c==d));}猜猜它的运行结果是什么?运行程序后,我们发现不对劲,得到了意想不到的运行结果,如下图所示。看到图中这个操作的结果,肯定有人会问,为什么会这样?出现这个结果的原因是因为Integer使用的Flyweight模式。看Integer的源码,publicfinalclassIntegerextendsNumberimplementsComparable{...publicstaticIntegervalueOf(inti){if(i>=IntegerCache.low&&i<=IntegerCache.high)returnIntegerCache.cache[i+(-IntegerCache.low)];返回新整数(i);}...}继续进入IntegerCache的源码查看low和high的值:privatestaticclassIntegerCache{//Minimumstaticfinalintlow=-128;//最大值,支持自定义staticfinalinthigh;//缓存数组staticfinalIntegercache[];static{//最大值可以通过属性配置改变inth=127;StringintegerCacheHighPropValue=sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");//如果设置了相应的属性,则使用该值if(integerCacheHighPropValue!=null){i=Math.max(i,127);//最大数组大小为Integer.MAX_VALUEh=Math.min(i,Integer.MAX_VALUE-(-low)-1);}catch(NumberFormatExceptionnfe){//如果e属性无法解析为int,忽略它。}}high=h;cache=newInteger[(high-low)+1];intj=low;//实例化所有low-high范围内的值并在使用for(intk时存入数组=0;k=127;}privateIntegerCache(){}}从上面可以看出,Integer源码中的valueOf()方法进行了条件判断。如果目标值是-128-127,直接从缓存中取值,否则创建一个新对象。实际上,在第一次使用Integer时,缓存会被初始化。范围的最小值为-128,最大值默认为127。然后从low到high的所有数据都会被初始化并存储在data中。默认情况下,从-128到127共256个数字会被循环实例化并存储在缓存数组中。准确的说,数组中应该存放这256个对象在内存中的地址。这里又有人会问了,为什么默认是-128-127,为什么不是-200-200或者其他值呢?那么JDK为什么要这样做呢?JavaAPI中是这样解释的:返回一个代表指定int值的Integer实例。如果不需要新的Integer实例,通常应优先使用此方法而不是构造函数Integer(int),因为此方法可能通过缓存频繁请求的值来产生明显更好的空间和时间性能。此方法将始终缓存-128到127(含)范围内的值,并可能缓存此范围之外的其他值Internal是最常用的。为了减少频繁创建对象带来的内存消耗,这里其实采用享元模式来提高空间和时间性能。JDK已经添加了这个默认范围并且它不是不可变的。我们可以在使用前通过设置-Djava.lang.Integer.IntegerCache.high=xxx或者设置-XX:AutoBoxCacheMax=xxx来修改缓存范围,如下图:图片图片后来发现了一个比较靠谱的解释:其实,在Java5中首次引入此功能时,范围固定为-127到+127。后来在Java6中,范围的最大值被映射到java.lang.Integer.IntegerCache.high,这是一个允许我们设置高位位数的VM参数。它根据我们的应用程序用例灵活地调整性能。应该选择-127到127这个数字范围的原因是什么?这被认为是广泛使用的整数范围。在程序中首次使用Integer必须花费额外的时间来缓存实例。JavaLanguageSpecification的原文解释如下:理想情况下,装箱一个给定的原始值p,总是会产生一个相同的引用。实际上,使用现有的实现技术这可能不可行。上述规则是一种务实的妥协。上面的最后一条要求始终将某些公共值装箱到无法区分的对象中。实现可以懒惰地或急切地缓存这些。对于其他值,此公式不允许对程序员部分的盒装值的身份进行任何假设。这将允许(但不要求)共享部分或全部这些参考资料。这确保在大多数常见情况下,行为将是所需的行为,而不会造成不应有的性能损失,尤其是在小型设备上。例如,内存限制较少的实现可能会缓存所有char和short值,以及在-32K到+32K范围内的int和long值。2关于整数和int的比较1)由整数变量实际上是对一个整合er对象引用,所以new生成的两个Integer变量永远不相等(因为new生成两个内存地址不同的对象)Integeri=newInteger(100);Integerj=newInteger(100);System.out.print(i==j);//false2)当一个Integer变量和一个int变量比较时,只要两个变量的值相等,结果就为真(因为包装类Integer和基本数据类型比较int时,java会自动解压成int再进行比较,实际上变成了两个int变量的比较)Integeri=newInteger(100);intj=100;System.out.print(i==j);//true3)将非new生成的Integer变量与newInteger()生成的变量进行比较时,结果为false。(因为①当变量值在-128-127之间时,非new生成的Integer变量指向java常量池中的对象,newInteger()生成的变量指向堆中新创建的对象,两者在内存中的地址不同;②当变量值在-128-127之间时,非new生成一个Integer变量时,javaAPI最终会根据newInteger(i)进行处理(参考下文第4条),最后两个Integer的地址也不一样)Integeri=newInteger(100);整数j=100;System.out.print(i==j);//false4)对两个非新生成的Integer对象进行比较,如果两个变量的值在-128和127之间,则比较结果为真,如果两个变量的值不在thisrange,比较结果为false3扩展知识在JDK中,此类应用不限于int,Flyweight模式也适用于以下wrapper类型,缓存值,但缓存范围不同,如下图下表:基本类型大小最小值最大包装类型缓存范围支持自定义boolean---Bloolean--char6bitUnicode0Unicode2(16)-1Character0~127Nobyte8bit-128+127Byte-128~127Noshort16bit-2(15)2(15)-1Short-128~127Noint32bit-2(31)2(31)-1Integer-128~127long64bit-2(63)2(63)-1Long-128~127nofloat32bitIEEE754IEEE754Float-double64bitIEEE754IEEE754Double-void---虚空--你觉得值得吗?4使用享元模式实现数据库连接池再比如我们经常使用的数据库连接池,因为使用Connection对象时主要的性能消耗是在建立和关闭连接的时候。为了提高Connection对象在调用时的性能,在调用前创建Connection对象并缓存,使用时直接从缓存中取值,用完后再放回去,实现了资源重用的目的。代码如下publicclassConnectionPool{privateVectorpool;privateStringurl="jdbc:mysql://localhost:3306/test";privateStringusername="root";privateStringpassword="root";privateStringdriverClassName="com.mysql.jdbc.Driver";privateintpoolSize=100;publicConnectionPool(){pool=newVector(poolSize);试试{Class.forName(driverClassName);for(inti=0;i0){Connectionconn=pool.get(0);pool.remove(conn);返回康恩;}returnnull;}publicsynchronizedvoidrelease(Connectionconn){pool.add(conn);}}这种连接池,普遍适用于开源框架,可以具有有效提升底层的运行能力。