通常情况下,我会锁定以下关键部分。
public class Cache {
private Object lockObject = new Object();
public Object getFromCache(String key) {
synchronized(lockObject) {
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
}
}
}
我的想法是避免竞争条件,这可能导致数据源被多次命中并且密钥被多次添加到缓存中。
现在如果两个线程几乎同时进入不同的缓存键,我仍然会阻止一个。
假设密钥是唯一的 - 通过锁定密钥锁仍然可以工作吗?
我认为它不会工作,因为我知道对象引用应该与锁定生效相同。我想这取决于它如何检查平等。
public class Cache {
public Object getFromCache(String key) {
synchronized(key) {
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
}
}
}
答案 0 :(得分:0)
public class Cache {
private static final Set<String> lockedKeys = new HashSet<>();
private void lock(String key) throws InterruptedException {
synchronized (lockedKeys) {
while (!lockedKeys.add(key)) {
lockedKeys.wait();
}
}
}
private void unlock(String key) {
synchronized (lockedKeys) {
lockedKeys.remove(key);
lockedKeys.notifyAll();
}
}
public Object getFromCache(String key) throws InterruptedException {
try {
lock(key);
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
} finally {
unlock(key);
}
}
}
答案 1 :(得分:-1)
每个对象都有一个隐式监视器,同步可以在其上运行。字符串对象可以在堆中创建,对于相同的字符集也可以是不同的(如果使用new
创建)或者可以来自池。只有当同步块在同一对象上同步时,两个线程才会使用synchronized块访问临界区。
同步字符串文字确实是一个坏主意。池中的字符串文字是共享的。试想一下,如果在代码的两个不同部分,你有两个同步的部分,并且你在两个String的引用上同步,但是使用带有相同字符集的字符串进行初始化,如果使用了来自池的字符串,则两个地方它将是同一个对象。即使这两个地方可能有不同的业务环境,但仍然可能最终导致您的申请被绞死。调试也很困难。
对于特定问题,如果在键上进行同步,则将解决目的。
您希望避免两个线程尝试写入而不读取最新的缓存值。每个条目都有不同的密钥。假设thread1和thread2想要访问相同的密钥,那么同一密钥对象上的同步将阻止它们进入同步块。同时,如果一个thread3想要访问另一个不同的密钥,那么它很可能会这样做。在这里,我们看到读取和写入与所有键的读取和写入的单个公共对象相比更快。到目前为止这么好但是如果假设您保留一个数组或任何其他类似的非线程安全数据结构来存储缓存值,则会出现问题。同时写入(对于两个或更多个不同的键)可能导致一个写入被另一个写入被相同的索引覆盖。
因此,它取决于缓存数据结构的实现,如何最好地为多线程环境中的更快读取和写入做好准备。