面对上面的问题,你该怎么回答?java
int 是咱们常说的整形数字,是 Java 的 8 个原始数据类型(Primitive Types,boolean、byte、short、char、int、float、double、long)之一。Java 语言虽然号称一切都是对象,但原始数据类型是例外。程序员
Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,而且提供了基本操做,好比数学运算、int 和字符串之间转换等。在 Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java 能够根据上下文,自动进行转换,极大地简化了相关编程。面试
关于 Integer 的值缓存,这涉及 Java 5 中另外一个改进。构建 Integer 对象的传统方式是直接调用构造器,直接 new 一个对象。可是根据实践,咱们发现大部分数据操做都是集中在有限的、较小的数值范围,于是,在 Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。编程
这个问题涵盖了 Java 里的两个基础要素:原始数据类型、包装类。谈到这里,就能够很是天然地扩展到自动装箱、自动拆箱机制,进而考察封装类的一些设计和实践。坦白说,理解基本原理和用法已经足够平常工做需求了,可是要落实到具体场景,仍是有不少问题须要仔细思考才能肯定。数组
咱们能够结合其余方面,来考察面试者的知识掌握程度和思考逻辑,好比:缓存
彷佛有太多内容能够探讨,咱们一块儿来分析一下。安全
什么是语法糖?能够简单理解为 Java 平台为咱们自动进行了一些转换,保证不一样的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的。
像前面提到的整数,javac 替咱们自动把装箱转换为 Integer.valueOf(),把拆箱替换为Integer.intValue(),这彷佛这也顺道回答了另外一个问题,既然调用的是 Integer.valueOf,天然可以获得缓存的好处啊。性能优化
如何程序化的验证上面的结论呢?并发
你能够写一段简单的程序包含下面两句代码,而后反编译一下。固然,这是一种从表现倒推的方法,大多数状况下,咱们仍是直接参考规范文档会更加可靠,毕竟软件承诺的是遵循规范,而不是保持当前行为。jvm
Integer integer = 1; int unboxing = integer ++; |
反编译输出:
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 8: invokevirtual #3 // Method java/lang/Integer.intValue:()I |
这种缓存机制并非只有 Integer 才有,一样存在于其余的一些包装类,好比:
自动装箱 / 自动拆箱彷佛很酷,在编程实践中,有什么须要注意的吗?
原则上,建议避免无心中的装箱、拆箱行为,尤为是在性能敏感的场合,建立 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不论是内存使用仍是处理速度,光是对象头的空间占用就已是数量级的差距了。
咱们其实能够把这个观点扩展开,使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景每每具备比较大的优点,用其替换掉包装类、动态数组(如 ArrayList)等能够做为性能优化的备选项。一些追求极致性能的产品或者类库,会极力避免建立过多对象。固然,在大多数产品代码里,并无必要这么作,仍是以开发效率优先。以咱们常常会使用到的计数器实现为例,下面是一个常见的线程安全计数器实现。
class Counter { private final AtomicLong counter = new AtomicLong(); public void increase() { counter.incrementAndGet(); } } |
若是利用原始数据类型,能够将其修改成
class CompactCounter { private volatile long counter; private static final AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class,"counter"); public void increase() { updater.incrementAndGet(this); } } |
考察是否阅读过、是否理解 JDK 源代码多是部分面试官的关注点,这并不彻底是一种苛刻要求,阅读并实践高质量代码也是程序员成长的必经之路,下面我来分析下 Integer 的源码。总体看一下 Integer 的职责,它主要包括各类基础的常量,好比最大值、最小值、位数等;前面提到的各类静态工厂方法 valueOf();获取环境变量数值的方法;各类转换方法,好比转换为不一样进制的字符串,如 8 进制,或者反过来的解析方法等。咱们进一步来看一些有意思的地方。首先,继续深挖缓存,Integer 的缓存范围虽然默认是 -128 到 127,可是在特别的应用场景,好比咱们明确知道应用会频繁使用更大的数值,这时候应该怎么办呢?
缓存上限值实际是能够根据须要调整的,JVM 提供了参数设置:
- XX:AutoBoxCacheMax=N
这些实现,都体如今java.lang.Integer源码之中,并实如今 IntegerCache 的静态初始化块里。
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() {} } .... } |
第二,咱们在分析字符串的设计实现时,提到过字符串是不可变的,保证了基本的信息安全和并发编程中的线程安全。若是你去看包装类里存储数值的成员变量“value”,你会发现,不论是Integer 仍是Boolean 等其余基本类型封装类,其value都被声明为“private final”,因此,它们一样是不可变类型!
前面提到了线程安全设计,你有没有想过,原始数据类型操做是否是线程安全的呢?
这里可能存在着不一样层面的问题:
“深刻java虚拟机”中提到,int等不大于32位的基本类型的操做都是原子操做,可是某些jvm对long和double类型的操做并非原子操做,这样就会形成错误数据的出现。
错误数据出现的缘由是:
在32位的虚拟机中,对于long和double变量,把它们做为2个原子性的32位值来对待,而不是一个原子性的64位值,
这样将一个long型的值保存到内存的时候,多是2次32位的写操做,
2个竞争线程想写不一样的值到内存的时候,可能致使内存中的值是不正确的结果。
一、写入高位32位值(线程2)
二、写入高位32位值(线程1)
三、写入低位32位值(线程1)
四、写入低位32位值(线程2)
这样内存中的值变成线程1的高32位值和线程2的低32位值的组合,是个错误的值。
书中还提到,上面出现问题的long和double变量是没有声明为volatile的变量。
volatile自己不保证获取和设置操做的原子性,仅仅保持修改的可见性。
可是java内存模型保证声明为volatile的long和double变量的get和set操做是原子的。
jvm spec引用中的最后一段说到,jvm能够很轻易的将变量操做变成原子性的,可是却受到了当前硬件的约束,由于流行的微处理器仍是32bit居多,所以64bit的变量须要拆分红两次,但若是是64bit处理器就能知足64bit变量的原子性操做了。
针对这个问题,找到了网上的一个总结: