对于Java的数据类型Integer,大多人认为不就很简单的一个数据类型吗?是的,我以前也不在意,感觉关于integer没什么可说,但是不深入源码你永远不知道它给你留下的坑。
一、谈谈integer留下的坑
首先我们来看一段代码。
package cn.shinelon.jvm;
public class TestInteger {
public static void main(String[] args) {
Integer i1=1;
Integer i2=1;
System.out.println(i1==i2);
Integer i3=129;
Integer i4=129;
System.out.println(i3==i4);
}
}
上面的代码只要接触过Java的人都能够看明白,不用过多解释,比较两个integer变量是否相等。估计一眼就可以看出答案,但是对不对呢?可能与大多人所预料的结果大相径庭,大多人觉得两个结果输出一定都是true,但是我们来看看运行结果。
这个结果着实让很多大吃一惊,为什么第二个会是false呢,i3和i4不都是129吗?怎么不相等了,下面来让我们看看这其中的原因:
使用javap -c命令反编译class文件:
我们可以看到Integer i1,i2,i3,i4这些语句都调用了Integer.valueOf()方法,因此,我们来看看valueOf()方法的源码。
上面的代码是Integer的部分源码,看valueOf就可以知道上面结果的原因了。下面我们来系统的的梳理一下integer的内部原理实现:
首先,初始化时该缓冲区大小为128+127+1=256的数组,当你声明一个integer变量是它会调用valueOf(int i)方法,当i>IntegerCache.low&&IntegerCache.high时它会去从缓冲区中根据索引下标直接取出相应的值,而当超出这个范围时它就会new一个新的对象。这就很好解释刚开始的问题了,“==”对于基本数据类型比较的是值是否相等,对于Integer它比较的是两个引用变量的内存地址的首地址是否相等,对于i1和i2他们的值都等于1,因此在调用valueof函数的时候他们没有超出缓冲区的范围,因此返回的是同一个内存地址的值,而i3和i4他们的值是129,刚好超出范围(-128~128),所以valueof会new一个新的integer对象,它们是不同的对象,因此首地址指向不同的内存,因此是false。
在通过上面的了解之后我们看到integer的内部原理,这里我们接着再看看integer在并发情况下的一些错误加锁方式:
package cn.just.thread.concurrent;
public class BadLockOnInteger implements Runnable{
public static Integer i=0;
public static BadLockOnInteger instance=new BadLockOnInteger();
@Override
public void run() {
for(int j=0;j<10000;j++){
synchronized (i) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
对于上面这段代码运行结果,这里就不卖关子了,理论情况下很多人看上去认为结果是20000,但是事实不是这样,结果会等到一个比20000小很多的数,比如12698,这是为什么呢?我们可以按照上面的方式使用javap命令反编译run()方法里面的代码,发现它任然调用了valueof方法,这就很好解释了,因为在该方法里它会在超出缓冲区范围后new一个新的对象,也就是说synchronized关键字加在两把不同的锁上面,因此两个线程不能同步,所以结果会比20000小很多。
怎么解决以上的问题呢?很简单,只需要将上面代码改为synchronized(instance)即可。
上面是个人对Integer的一些小小的总结以及解读,如有不足之处还请指教!