引言
一转眼已经两年多没写多博客了;一转眼也要快工作两三年了;一转眼我又开始写Java代码了。希望自己可以坚持写写博客,总结总结的习惯!加油。
今天在调试代码的时候,发现两个毫不相关的thread用jstack看竟然其中一个在等待另一个的线程持有的锁,很是奇怪。经过研究,是因为Integer类的实现机制导致的。
一、异常阻塞代码
1 package xxx;
2
3 public class TestDeadLock {
4 public static void main(String[] argvs) {
5
6 new A("threadA").start();
7 new B("threadB").start();
8 try {
9 Thread.sleep(15000);
10 } catch (Exception e) {
11
12 }
13 }
14 }
15 class A extends Thread {
16 public A(String name) {super(name);}
17 private final Object updateLock = 0;
18 @Override
19 public void run() {
20 System.out.println(updateLock.getClass());
21 synchronized (updateLock) {
22 System.out.println(System.currentTimeMillis() + " in A");
23 try {
24 sleep(100000);
25 } catch (InterruptedException e) {
26
27 }
28 }
29 }
30 }
31 class B extends Thread {
32 public B(String name) {super(name);}
33 private final Object updateLock = 0;
34 @Override
35 public void run() {
36 System.out.println(updateLock.getClass());
37 synchronized (updateLock) {
38 System.out.println(System.currentTimeMillis() + " in B");
39 try {
40 sleep(100000);
41 } catch (InterruptedException e) {
42
43 }
44 }
45 }
46 }
上面的代码很简单,建立了两个thread,分别打印一句话,每个线程拥有一个本地的变量updateLock用来进行自己线程内部的同步。问题就出在了这个updateLock上。运行上面的代码会发现这两个线程会互相阻塞,updateLock为线程的内部对象,为什么会互相阻塞呢?问题就出现在了17以及33行的0赋值的问题。0这个值本身是一个基本类型int,updateLock是一个Object的类型,因为实际上会把0强转为Integer类型赋值给updateLock对象,那么Java内部是如何完成这个操作的呢,这个赋值是依赖于Integer的ValueOf方法实现的。
二、Integer的ValueOf实现
1 public static Integer valueOf(int i) {
2 if (i >= IntegerCache.low && i <= IntegerCache.high)
3 return IntegerCache.cache[i + (-IntegerCache.low)];
4 return new Integer(i);
5 }
可以看到valueOf的实现依赖于IntegerCache的实现,从名字就能看出来IntegerCache是一个Interger的缓存,IntegerCache缓存这从-128到127(上限127可以通过jvm配置进行修改)。valueOf的逻辑就是如果存在缓存,返回缓存,不存在就返回一个新的Integer对象。
三、IntegerCache的实现
1 private static class IntegerCache {
2 static final int low = -128;
3 static final int high;
4 static final Integer cache[];
5
6 static {
7 // high value may be configured by property
8 int h = 127;
9 String integerCacheHighPropValue =
10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11 if (integerCacheHighPropValue != null) {
12 try {
13 int i = parseInt(integerCacheHighPropValue);
14 i = Math.max(i, 127);
15 // Maximum array size is Integer.MAX_VALUE
16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17 } catch( NumberFormatException nfe) {
18 // If the property cannot be parsed into an int, ignore it.
19 }
20 }
21 high = h;
22
23 cache = new Integer[(high - low) + 1];
24 int j = low;
25 for(int k = 0; k < cache.length; k++)
26 cache[k] = new Integer(j++);
27
28 // range [-128, 127] must be interned (JLS7 5.1.7)
29 assert IntegerCache.high >= 127;
30 }
31
32 private IntegerCache() {}
33 }
IntegerCache的实现比较简单,其实就是内部维护了一个cache数组,提前new好对象,需要的时候直接返回,不再进行对象的创建,因为对象的创建成本是很高的,这样做可以提高效率。这里有一个有意思的地方是jvm需要通过java.lang.Integer.IntegerCache.high配置来修改cache缓存的上限,但是没有提供修改下限的配置,不知道这样做的目的是什么,还有待于考察。
四、深入Object updateLock = 0
因为第一节中的额代码产生了线程互相阻塞的问题,所以可以猜测出两个线程中的updateLock其实是一个相同的对象,即两个线程中的updateLock是两个相同的Integer对象,而相同的Integer对象很大可能是产生自Integer的valueOf方法返回的对象。为了验证我们的猜想,我们将断点断到java.lang.Interger的valueOf函数上进行debug,查看调用栈,如下图所示
上面的图验证了我们的猜想。因此如何解决这个问题也就很简单了。可以将一个new Object()赋值给updateLock就可以了。
总结
如果一个对象的目的是用在synchronized关键字后进行同步处理,那么不允许进行int或其他内部类型的赋值,如第一节中的示例代码,标准的做法应该如下
final Object updateLock = new Object();