用字符串加锁

问题场景

使用 redis 做缓存, 在缓存中查询不到时要先加锁再去 db 中查, 这时用查询 redis 时的锁作为 key 进行加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public <K, T> T findCache(K key, long timeout, TimeUnit unit, Class<T> clazz, Callable<T> loadBack) {
T result = redisService.get(key, clazz);
if (null != result) {
logger.info("load cache ======== {}.", key);
return result;
} else {
synchronized (pool.intern(key)) {
result = redisService.get(key, clazz);
if (null != result) {
logger.info("load cache ======== {}.", key);
return result;
}
result = doLoadBack(loadBack);
if (result != null) {
redisService.set(key, result, timeout, unit);
}
return result;
}
}
}

不能直接用 String 对象作为锁

synchronizd 加锁是基于对象进行的, value内容相同的两个 String, 如果不是同一个对象, 期望的加锁行为就无法完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class NicoThread extends Thread {
private String s;
private NicoThread(String s) {
this.s = s;
}
@Override
public void run() {
synchronized (s) {
try {
String name = Thread.currentThread().getName();
System.out.println(name + ": begin sleep");
Thread.sleep(2000);
System.out.println(name + ": end sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {

String s1 = "niconiconi";
String s2 = new String("niconiconi");

new NicoThread(s1).start();
new NicoThread(s2).start();

}
}
1
2
3
4
5
6
// 输出
Thread-0: begin sleep
Thread-1: begin sleep
Thread-1: end sleep
Thread-0: end sleep
// 同步失败

使用 intern() 对象加锁?

value内容相同的两个 String, intern() 后得到的是 String 常量池中的同一个对象, 可以完成期望的锁操作

1
2
// synchronized(s) {}
synchronized(s.intern()) {}

intern() 的 gc 问题

在jdk7下慎用String.intern()作为synchronized的对象锁 - 绝望生鱼片 - 博客园

深入解析String#intern

频繁调用 intern() 可能产生内存问题, 为了解决这个问题, 可以自定义一个 ConcurrentHashMap<String, Object> 用来储存传入的 key 与实际锁的对象

guava 的 interner 实现

api文档

1
2
3
private Interner<String> interner = Interners.<String>newWeakInterner();

synchronized(interner.intern(str)) {}

类似数据结构

guava 的 cache 是在内存中用 map 做缓存

https://google.github.io/guava/releases/23.5-jre/api/docs/


参考资料

When is it beneficial to flyweight Strings in Java? - Stack Overflow

multithreading - Synchronizing on String objects in Java - Stack Overflow

locking - Simple Java name based locks? - Stack Overflow