在前期系统开发中,系统升级成了区域版本的,一个系统拥有多个机构,机构ID字段是字符串类型的,在java中由于字符串可以缓存在常量池中,之前都是用机构ID作为锁条件的,这样可以保证,同一个机构是串行的而不同机构是并行的。
最近使用新框架,机构ID由String类型升级成了Long类型,这样再直接使用机构ID作synchronized的锁对象肯定是不行的,因为并没有常量池来缓存Long类型数据,不过跟Integer类型一样,Long类型自身也是做了缓存的:
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
可以看到缓存区域也是[-128,127],在这个区域内使用栈赋值的方式声明变量,可以保证引用地址是同一个,超过这个区域就不行了,我不能保证机构ID只在这个范围内啊,怎么解决呢?
首先想到的就是把机构ID重新转成字符串,不就行了吗。
Long.toString方法源码:
public static String toString(long i) {
if (i == Long.MIN_VALUE)
return "-9223372036854775808";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
可以看到,他每次返回的都是一个new出来的字符串对象,这样锁对象就变了,是锁不住的。这个时候String的intern方法就出场了。
在jdk1.6中,该方法把字符串的值复制到常量区,然后返回常量区里这个字符串的值;
在jdk1.7里,该方法在常量区记录该字符串首次出现的实例引用,然后返回该地址,常量区可以保存字面量也可以保存字符串对象在堆中的引用。
因为jdk7中intern方法是(在常量区找不到该字符串时)将该字符串对象在堆里的引用注册到常量区,以后使用相同字面量(双引号形式)声明的字符串对象都指向该地址,也就是该字符串在堆中的地址。用代码说明一下:
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = (new String("a") + new String("bc"));
System.out.println(str1 == str2);// false
System.out.println(str1.intern() == str2.intern());// true
}
上面的str1和str2地址肯定是不相同的,但是用于字面量是相同的所以,intern方法的返回值再比较就是相同的啦,这样问题就就解决了:
Long jiGouId = Loginutils.currentDoneOrgId();
String lockCondition = Long.toString(jiGouId).intern();
synchronized (lockCondition) {
// dosomething
}