这是一个关于Java常见并发问题的投票。例如,经典死锁或竞态条件,或者Swing中的EDT线程错误等。我对可能存在的问题广度和最常见的问题都很感兴趣。因此,请在每个评论中留下一个特定的Java并发错误的答案,并投票支持您遇到过的问题。
这是一个关于Java常见并发问题的投票。例如,经典死锁或竞态条件,或者Swing中的EDT线程错误等。我对可能存在的问题广度和最常见的问题都很感兴趣。因此,请在每个评论中留下一个特定的Java并发错误的答案,并投票支持您遇到过的问题。
我最痛苦的并发问题之一是由两个不同的开源库执行以下操作引起的:
#1 most painful表示这是最令人痛苦的问题之一。
private static final String LOCK = "LOCK"; // use matching strings
// in two different libraries
public doSomestuff() {
synchronized(LOCK) {
this.work();
}
}
乍一看,这似乎是一个相当琐碎的同步示例。然而,由于Java中的字符串被内部化,字面字符串"LOCK"
实际上是java.lang.String
的同一实例(尽管它们从根本上声明不同)。结果显然很糟糕。
synchronized("LOCK") {this.work();}
:P - Peter Lawrey我见过的最常见的并发问题是,没有意识到一个线程写入的字段不一定会被另一个线程看到。这种情况的常见应用:
class MyThread extends Thread {
private boolean stop = false;
public void run() {
while(!stop) {
doSomeWork();
}
}
public void setStop() {
this.stop = true;
}
}
只要stop
不是volatile,或者setStop
和run
没有synchronized,就不能保证它能正常工作。这个错误特别麻烦,因为在实践中99.999%情况下它不会产生影响,因为读取线程最终会看到更改,但我们不知道他看到更改的时间。一个经典的问题是在同步对象时更改正在同步的对象:
synchronized(foo) {
foo = ...
}
其他并发线程正在同步不同的对象,而这个代码块并没有提供你所期望的互斥保护。
synchronized(locks){key=locks.get(key);}synchronized(key){objects.put(key,modify(objects.get(key)));}
,当然要在objects
上适当同步。 - yingted一种常见的问题是在没有同步的情况下从多个线程中使用Calendar
和SimpleDateFormat
这样的类(通常通过缓存它们在静态变量中)。 这些类不是线程安全的,因此多线程访问最终会导致状态不一致的奇怪问题。
Collections.synchronizedXXX()
返回的对象在迭代或多个操作期间没有正确同步:
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
...
if(!map.containsKey("foo"))
map.put("foo", "bar");
那是错误的。尽管单个操作被synchronized
同步,但在调用contains
和put
之间,地图的状态可能会被另一个线程更改。应该这样:
synchronized(map) {
if(!map.containsKey("foo"))
map.put("foo", "bar");
}
或使用ConcurrentMap
实现:
map.putIfAbsent("foo", "bar");
双重检查锁定。总的来说。
这种编程范式,我在BEA工作期间开始学习其中的问题,人们通常会按以下方式检查单例:
public Class MySingleton {
private static MySingleton s_instance;
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) { s_instance = new MySingleton(); }
}
return s_instance;
}
}
这种方法从来不起作用,因为另一个线程可能已经进入了同步块,并且s_instance现在不再为null。所以自然而然的改变是将其改为:
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) {
if(s_instance == null) s_instance = new MySingleton();
}
}
return s_instance;
}
这也行不通,因为Java内存模型不支持它。你需要将s_instance声明为volatile才能使其工作,即使如此,它只能在Java 5上工作。
那些不熟悉Java内存模型细节的人经常会搞砸这个。
虽然可能不完全符合您的要求,但我遇到的最常见的并发相关问题(可能是因为它会在正常的单线程代码中出现)是 java.util.ConcurrentModificationException
,这通常是由以下情况引起的:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }
很容易认为同步集合提供的保护比它们实际上提供的更多,并忘记在调用之间持有锁。我已经看到过这个错误几次:
List<String> l = Collections.synchronizedList(new ArrayList<String>());
String[] s = l.toArray(new String[l.size()]);
toArray()
和 size()
方法本身都是线程安全的,但是size()
是与toArray()
分开计算的,并且在这两个调用之间没有保持列表上的锁。String[]
,它比所需容纳列表中所有元素所需要的要大,并且尾部有空值。很容易认为,因为对列表的两个方法调用出现在单行代码中,所以这种情况在某种程度上是原子操作,但实际上并非如此。我工作中经常遇到的最常见的错误是程序员在事件分发线程(EDT)上执行长时间的操作,如服务器调用,锁定GUI几秒钟并使应用程序无响应。
在循环中忘记使用wait()(或Condition.await()),检查等待条件是否实际为true。如果没有这样做,你会遇到因虚假的wait()唤醒而导致的错误。标准用法应该是:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
// do stuff based on condition being true
}