前言
synchronized(Object)
不能用String常量
因为字符串常量是在内存中同一个地方。如果两个线程锁一个,可能导致死锁。一个线程多次可能导致重入。
也不能用Integer,Long等基础类型。
因为当锁定int或者long时, jvm就会调用Interger.valueOf()生成一个新的对象,所以锁不会失效
看源码可知,java会把-128到127所对应的包装类都给缓存起来,分别存入了IntegerCache和LongCache
而当锁的值大于127之后,就会新建一个对象,在堆上产生,这时,锁就会失效。
synchoronized 为什么不能锁 int 或者 long 类型
1 测试代码
@Test
fun lockWithPrimitiveType() {
val startTime = Instant.now()
Thread {
while (true) {
synchronized(100L) {
TimeUnit.SECONDS.sleep(2L) // 模拟业务代码耗时
val endTime = Instant.now()
val duration = Duration.between(startTime, endTime)
println("current thread:${Thread.currentThread().name}, I am guild one, seconds: ${duration.seconds}")
}
}
}.start()
Thread {
while (true) {
synchronized(100L) {
TimeUnit.SECONDS.sleep(2L) // 模拟业务代码耗时
val endTime = Instant.now()
val duration = Duration.between(startTime, endTime)
println("current thread:${Thread.currentThread().name}, I am guild two, seconds: ${duration.seconds}")
}
}
}.start()
while (true) {}
}
让我们来看一下打印结果:
current thread:Thread-3, I am guild one, seconds: 2
current thread:Thread-4, I am guild two, seconds: 4
current thread:Thread-4, I am guild two, seconds: 6
current thread:Thread-4, I am guild two, seconds: 8
current thread:Thread-4, I am guild two, seconds: 10
current thread:Thread-4, I am guild two, seconds: 12
current thread:Thread-4, I am guild two, seconds: 14
current thread:Thread-4, I am guild two, seconds: 16
current thread:Thread-4, I am guild two, seconds: 18
current thread:Thread-4, I am guild two, seconds: 20
从日志来看,Thread-3线程在执行业务代码的时候,Thread-4线程并没有被执行(阻塞等待)。Thread-4线程在执行业务代码的时候, Thread-3线程也没有被执行。由此可见,锁已经生效了。于是得出了一个结论:“synchornized 锁一个 long 类型的整数是可行的”。
可事实是真是这样吗?
下面对代码做一个简单的修改,将 synchronzied(100L) 改为 synchronized(1000L),再进行测试。
@Test
fun lockWithPrimitiveType() {
val startTime = Instant.now()
Thread {
while (true) {
synchronized(1000L) { // 将 100L 改为 1000L
TimeUnit.SECONDS.sleep(2L) // 模拟业务代码耗时
..... // 与之前代码一致
}
}
}.start()
Thread {
while (true) {
synchronized(1000L) { // 将 100L 改为 1000L
TimeUnit.SECONDS.sleep(2L) // 模拟业务代码耗时
...... // 与之前代码一致
}
}
}.start()
while (true) {}
}
让我们继续看一下打印结果:
current thread:Thread-4, I am guild two, seconds: 2
current thread:Thread-3, I am guild one, seconds: 2
current thread:Thread-4, I am guild two, seconds: 4
current thread:Thread-3, I am guild one, seconds: 4
current thread:Thread-4, I am guild two, seconds: 6
current thread:Thread-3, I am guild one, seconds: 6
current thread:Thread-3, I am guild one, seconds: 8
current thread:Thread-4, I am guild two, seconds: 8
current thread:Thread-4, I am guild two, seconds: 10
current thread:Thread-3, I am guild one, seconds: 10
这个时候,我们会发现,Thread-4线程在执行业务代码的时候,Thread-3线程同时也在执行业务代码,那么这个时候,锁为什么没有生效呢?
答案是:synchronized 只能锁对象,那么当我们传入 int 类型或者 long 类型整数的时候,java 会调用 Integer.valueOf() 或者 Long.valueOf() 方法将它们转为一个新的对象,所以锁不会生效。
那么为什么锁 100L 这个整数时,锁会生效,这个时候我们看源码可以得知,Java 会把 -128 到 127 所对应的包装类对象都给缓存起来,分别存入了 IntegerCache 和 LongCache 对象里。
2 源码分析
Integer.valueOf() 方法
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) // 如果 i >= -128 且 i <= 127
return IntegerCache.cache[i + (-IntegerCache.low)]; // 返回 IntegerCache 里的对象
return new Integer(i); // 返回一个新对象
}
IntegerCache 类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127
...... // 省略非关键性代码
heigh = h
cache = new Integer[(high - low) + 1]; // 设置数组的大小(256)
int j = low;
for(int k = 0; k < cache.length; k++) // 为 -128 到 127 之间的整数创建 Integer 对象
cache[k] = new Integer(j++);
...... // 省略非关键性代码
}
private IntegerCache() {}
}
Long.valueOf() 方法
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset]; // 返回 LongCache 里的对象
}
return new Long(l); // 返回新对象
}
LongCache 类
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
可以发现,Integer.valueOf() 与 Long.valueOf 实现方法略有不同,IntegerCache 和 LongCache 实现方法也略有不同,可是结果都是一样的:都是对-128 到 127 之间的整数所对应的包装类对象进行缓存。
为什么实现没有保持统一,我们也不得而知了,我猜可能是实现代码的作者不同吧,哈哈哈。
3 阿里巴巴开发规范
在阿里巴巴开发规范,OOP 规约里第 7 条也有提到这么一项的强制规则。