【发布时间】:2017-02-01 00:43:19
【问题描述】:
一段时间以来,我们一直在研究线程错误,但不确定这是怎么可能的。下面是我们代码中的一个最小化示例。有一个缓存保存着从数据库中检索到的数据(或者:就本示例而言:“一个冗长的同步操作”)。有一个线程用于重新加载缓存,而其他线程尝试查询缓存。有一段时间缓存为空,等待重新加载。在这段时间内它不应该是可查询的,我们试图通过同步访问缓存的方法来强制执行这一点——读取和写入。但是,如果您运行该课程一段时间,您将在search() 中获得 NPE。这怎么可能?
Java 文档声明“不可能对同一对象的同步方法的两次调用交错。当一个线程正在为一个对象执行同步方法时,所有其他线程为同一对象块调用同步方法 (暂停执行),直到第一个线程处理完对象”。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CacheMultithreading01 {
private long dt = 1000L;
public static void main(String[] args) {
CacheMultithreading01 cm = new CacheMultithreading01();
cm.demonstrateProblem();
}
void demonstrateProblem() {
QueryableCache cache = new QueryableCache();
runInLoop("Reload", new Runnable() {
@Override
public void run() {
cache.reload();
}
});
runInLoop("Search", new Runnable() {
@Override
public void run() {
cache.search(2);
}
});
// If the third "runInLoop" is commented out, no NPEs
runInLoop("_Clear", new Runnable() {
@Override
public void run() {
cache.clear();
}
});
}
void runInLoop(String threadName, Runnable r) {
new Thread(new Runnable() {
@Override
public synchronized void run() {
while (true) {
try {
r.run();
} catch (Exception e) {
log("Error");
e.printStackTrace();
}
}
}
}, threadName).start();
}
void log(String s) {
System.out.format("%d %s %s\n", System.currentTimeMillis(), Thread
.currentThread().getName(), s);
}
class QueryableCache {
private List<Integer> cache = new ArrayList<>();
public synchronized void reload() {
clear();
slowOp(); // simulate retrieval from database
cache = new ArrayList<>(Arrays.asList(1, 2, 3));
}
public synchronized void clear() {
cache = null;
}
public synchronized Integer search(Integer element) {
if (cache.contains(element))
return element;
else
return null;
}
private void slowOp() {
try {
Thread.sleep(dt);
} catch (InterruptedException e) {
}
}
}
}
//java.lang.NullPointerException
//at examples.multithreading.cache.CacheMultithreading01$QueryableCache.search(CacheMultithreading01.java:73)
//at examples.multithreading.cache.CacheMultithreading01$2.run(CacheMultithreading01.java:26)
//at examples.multithreading.cache.CacheMultithreading01$4.run(CacheMultithreading01.java:44)
//at java.lang.Thread.run(Thread.java:745)
我们不明白为什么即使代码是同步的也会发生 NPE。如果我们注释掉对runInLoop 的第三次调用(调用cache.clear),我们也不明白为什么NPE 会停止发生。
我们也尝试使用ReentrantReadWriteLock 来实现锁定 - 结果是一样的。
【问题讨论】:
-
所以在这段代码中缓存对象没有同步。那么 QueryableCache 做同步吗?您可以发布该代码吗? NPE 似乎位于 QueryableCache 的一些内部数据结构上。
-
slowOp() 是否/应该返回一些东西?我在这里什么都不做。
-
贴出的代码是自给自足的。它运行并抛出 NPE。 QueryableCache 的所有方法都是同步的(在 QueryableCache 实例的内在锁上,只有一个)。您可以使用代码,例如将私有成员添加到 QueryableCache 并在其上进行同步。在我们的测试中,结果相同。
-
@m0skit0 你试过运行它吗?它是一个 Java 类,可以在发布时运行(无依赖关系)。你得到了什么错误?
-
哦,哇,我的错。我完全错过了代码块上的滚动。对不起。
标签: java multithreading