[英]Java Threading Unexpected Behavior
我们一直在研究线程错误,但不确定这是怎么回事。 以下是我们代码中的最小化示例。 有一个缓存保存从数据库中检索的数据(或者:“一个冗长的同步操作”,就这个例子而言)。 有一个用于重新加载缓存的线程,而其他线程尝试查询缓存。 有一段时间缓存为空,等待重新加载。 在此期间它不应该是可查询的,我们试图通过同步访问缓存的方法来强制执行此操作 - 包括读取和写入。 然而,如果你运行这个类一段时间,你将在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
实现锁定 - 结果是一样的。
如果cache
为空,则必须检入search
方法。 否则,在以前在clear
-method中将cache
设置为null的情况下,在search
调用contains
NullPointerException
。
由于您没有任何更高级的锁定,因此可以连续调用clear()
和search()
。 这显然会导致NPE。
调用reload()
和search()
不会导致问题,因为在重新加载时,缓存会在同步块内被清除,然后重建,从而阻止在其间执行其他(搜索)操作。
为什么有一个clear()
方法会使cache
处于“坏”状态( search()
甚至不检查)?
“有一段时间缓存为空,等待重新加载。” 这是你的问题: clear
将items设置为null,然后返回,释放同步锁,允许其他人访问。 最好将“新”赋值设为原子而不是clear()
。
假设slowOp()需要返回缓存的数据( private List<Integer> slowOp())
,则在分配数据之前检索该数据
ArrayList<Integer> waitingForData = slowOp(); cache = watingForData;
只有在数据可用后才会“更新”缓存。 赋值是一个原子操作,在更新引用时没有任何东西可以访问缓存 。
同步正常工作。
问题是clear
方法将cache放到null
。 并且无法保证在search
之前调用reload
方法。
另外,请注意该方法reload
,它不会释放锁定。 因此,当您等待slowOp
完成时,其他方法无法执行。
三个不同的线程调用高速缓存的clear()search()和reload()而没有明确的交错。 由于交错不保证为clear()和search()线程获取锁定序列,因此搜索线程可能有机会在clear()线程之后获取对象的锁定。 在这种情况下,搜索将导致NullPointerException。
您可能必须在搜索对象中检查等于null的缓存,并且可以在search()方法中执行reload()。 这将保证搜索结果或返回null(如果适用)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.