引言

一转眼已经两年多没写多博客了;一转眼也要快工作两三年了;一转眼我又开始写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,查看调用栈,如下图所示

java线程阻塞排查流程_赋值

上面的图验证了我们的猜想。因此如何解决这个问题也就很简单了。可以将一个new Object()赋值给updateLock就可以了。

总结

如果一个对象的目的是用在synchronized关键字后进行同步处理,那么不允许进行int或其他内部类型的赋值,如第一节中的示例代码,标准的做法应该如下

final Object updateLock = new Object();