在Java中遇到的最常见的并发问题是什么?

这是对Java中常见并发问题的sorting。 一个例子可能是典型的死锁或竞争条件,或者可能是EDT线程Swing中的错误。 我对广泛的可能的问题感兴趣,而且对什么问题最为常见。 所以,请给每个评论留下一个Java并发错误的具体答案,如果你看到一个你遇到的错误,就投票表决。

我所见过的最常见的并发问题是没有意识到由一个线程编写的字段不能保证被不同的线程看到。 这是一个常见的应用:

class MyThread extends Thread { private boolean stop = false; public void run() { while(!stop) { doSomeWork(); } } public void setStop() { this.stop = true; } } 

只要停止不稳定setStoprun同步这不保证工作。 这个错误是特别恶毒的,因为99.999%在实践中并不重要,因为读者线最终会看到变化 – 但是我们不知道他多快看到它。

当两个不同的开源库做了这样的事情时,我遇到了最痛苦的并发问题:

 private static final String LOCK = "LOCK"; // use matching strings // in two different libraries public doSomestuff() { synchronized(LOCK) { this.work(); } } 

乍一看,这看起来像一个非常平凡的同步的例子。 然而; 由于string是在Java中实现的,string“LOCK”原来是java.lang.String的同一个实例(尽pipe它们是完全不同的)。结果显然是不好的。

一个典型的问题是在同步的同时改变你正在同步的对象:

 synchronized(foo) { foo = ... } 

其他并发线程然后在不同的对象上同步,并且这个块不提供你期望的相互排斥。

一个常见的问题是使用像Calendar和SimpleDateFormat这样的来自多个线程的类(通常通过将它们caching在一个静态variables中)而不同步。 这些类不是线程安全的,因此multithreading访问最终会导致状态不一致的奇怪问题。

双选locking。 总的来说。

我开始学习在东亚银行工作时遇到的问题的范例是,人们会用以下方式检查单身人士:

 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不再为空。 那么自然的变化就是:

  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内存模型复杂性的人一直在搞这个。

对Collections.synchronizedXXX()返回的对象没有正确的同步,特别是在迭代或多次操作期间:

 Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>()); ... if(!map.containsKey("foo")) map.put("foo", "bar"); 

那是错的 尽pipe单个操作被同步,调用containsput之间的映射状态可以被另一个线程改变。 它应该是:

 synchronized(map) { if(!map.containsKey("foo")) map.put("foo", "bar"); } 

或者使用ConcurrentMap实现:

 map.putIfAbsent("foo", "bar"); 

虽然可能并不完全是你要求的,我遇到的最频繁的并发相关的问题(可能是因为它出现在普通的单线程代码中)是

java.util.ConcurrentModificationException

造成的事情如:

 List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c")); for (String string : list) { list.remove(string); } 

我们在工作中遇到的最常见的错误是程序员在EDT上执行长时间操作(如服务器调用),lockingGUI几秒钟,使应用程序无响应。

忘记在循环中等待()(或Condition.await()),检查等待条件是否为真。 如果没有这个,你会遇到来自虚假wait()唤醒的错误。 规范使用应该是:

  synchronized (obj) { while (<condition does not hold>) { obj.wait(); } // do stuff based on condition being true } 

可以很容易地认为同步collections给予您比实际更多的保护,并忘记locking呼叫之间的locking。 如果几次看到这个错误:

  List<String> l = Collections.synchronizedList(new ArrayList<String>()); String[] s = l.toArray(new String[l.size()]); 

例如,在上面的第二行中,toArray和size()方法本身都是线程安全的,但size()和toArray()分开计算,List之间的锁不保持两个电话。 如果使用另一个线程同时从列表中删除项目来运行此代码,则迟早您将返回一个新的String [],它比保存列表中的所有元素所需的更大,并且在尾部中具有空值。 很容易想到,因为对列表的两个方法调用发生在一行代码中,这在某种程度上是一个primefaces操作,但事实并非如此。

另一个常见的错误是exception处理不好。 当后台线程抛出一个exception时,如果处理不当,可能根本看不到堆栈跟踪。 或者,您的后台任务可能会停止运行,并且不会再次启动,因为您无法处理exception。

在我和Brian Goetz上课之前,我并没有意识到通过同步设置器变异的私有字段的非同步getter 不能保证返回更新的值。 只有当一个variables被读取和写入的同步块保护时,才能保证variables的最新值。

 public class SomeClass{ private Integer thing = 1; public synchronized void setThing(Integer thing) this.thing = thing; } /** * This may return 1 forever and ever no matter what is set * because the read is not synched */ public Integer getThing(){ return thing; } } 

认为你正在编写单线程代码,但使用可变静态(包括单身)。 显然它们将在线程之间共享。 这经常出乎意料地发生。

任意方法调用不应在同步块内进行。

Dave Ray在他的第一个答案中提到了这个问题,事实上,我也遇到了一个死锁问题,这个死锁问题也是在同步方法中调用侦听器的方法。 我认为更普遍的一个教训是,方法调用不应该从同步块中“疯狂” – 你不知道调用是否会长时间运行,导致死锁,或者其他什么。

在这种情况下,通常一般情况下,解决scheme是减less同步块的范围,以保护关键的私有代码段。

此外,由于我们现在正在访问同步块之外的侦听器集合,因此我们将其更改为写入时复制集合。 或者我们可以简单地做一个收集的防御副本。 重点是,通常有安全访问未知对象集合的替代方法。

我遇到的最近发生的与并发有关的错误是一个对象,在它的构造函数中创build了一个ExecutorService,但是当对象不再被引用时,它从来没有closures过ExecutorService。 因此,在数周的时间里, 数以千计的线程泄露,最终导致系统崩溃。 (从技术上讲,它并没有崩溃,但是在继续运行的时候它确实停止了正常运行。)

从技术上讲,我想这不是一个并发问题,但这是一个与使用java.util.concurrency库有关的问题。

我最大的问题一直是僵局,特别是因为持有locking而被解雇的听众造成的。 在这些情况下,在两个线程之间进行反转locking是非常容易的。 在我的情况下,在一个线程中运行的模拟和在UI线程中运行的模拟的可视化之间。

编辑:移动第二部分来分开答案。

不平衡的同步,特别是针对地图似乎是一个相当普遍的问题。 许多人认为同步放入一个Map(不是一个ConcurrentMap,而是一个HashMap)而不是同步get就足够了。 然而,这可能导致在重新散列期间的无限循环。

然而,同样的问题(部分同步)可能发生在你读写共享状态的任何地方。

我遇到了一个与Servlets的并发问题,当有可变的字段将由每个请求设置。 但是对于所有的请求只有一个servlet实例,所以这在单个用户环境中完美工作,但是当多个用户请求servlet不可预知的结果时。

 public class MyServlet implements Servlet{ private Object something; public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException{ this.something = request.getAttribute("something"); doSomething(); } private void doSomething(){ this.something ... } } 

不完全是一个错误,但最糟糕的罪恶是提供一个你打算让其他人使用的库,但不能说明哪些类/方法是线程安全的,哪些只能从单个线程调用。

更多的人应该使用Goetz书中描述的并发注释(例如@ThreadSafe,@GuardedBy等)。

共享数据结构中的可变类

 Thread1: Person p = new Person("John"); sharedMap.put("Key", p); assert(p.getName().equals("John"); // sometimes passes, sometimes fails Thread2: Person p = sharedMap.get("Key"); p.setName("Alfonso"); 

发生这种情况时,代码要比这个简单的例子复杂得多。 复制,查找和修复这个bug是很困难的。 也许可以避免,如果我们可以将某些类标记为不可变,并将某些数据结构标记为仅保存不可变对象。

在由string文字定义的string文字或常量上进行同步(可能)是一个问题,因为string文字是被执行的,并且将被JVM中的其他人使用相同的string文字共享。 我知道这个问题已经出现在应用程序服务器和其他“容器”场景中。

例:

 private static final String SOMETHING = "foo"; synchronized(SOMETHING) { // } 

在这种情况下,使用string“foo”locking的任何人都共享同一个锁。

在类的构造函数中启动线程是有问题的。 如果类是扩展的,则可以在子类的构造函数执行之前启动线程。

我相信未来Java的主要问题将是构造函数的(缺less)可见性保证。 例如,如果您创build以下类

 class MyClass { public int a = 1; } 

然后从另一个线程读取MyClass的属性a ,MyClass.a可以是0或1,具体取决于JavaVM的实现和情绪。 今天“a”为1的机会非常高。 但在未来的NUMA机器上可能会有所不同。 许多人不知道这一点,并相信在初始化阶段他们不需要关心multithreading。

我经常做的最愚蠢的错误就是在调用对象的notify()或wait()之前忘记同步。

使用本地“新对象()”作为互斥体。

 synchronized (new Object()) { System.out.println("sdfs"); } 

这是无用的。

另一个常见的“并发”问题是在完全不需要时使用同步代码。 例如,我仍然看到程序员使用StringBuffer甚至java.util.Vector (作为方法局部variables)。

locking受保护但通常被连续访问的多个对象。 我们遇到了几个不同的代码以不同顺序获得锁的情况,导致了死锁。

没有意识到内部阶层的this不是外部阶级的this 。 通常在实现Runnable的匿名内部类中。 根本问题是,因为同步是所有Object的一部分,所以实际上没有静态types检查。 我在usenet上至less看到了两次,而且它也出现在Brian Goetz'z Java并发实践中。

BGGA封闭不受此影响,因为封闭没有thisthis引用外部类)。 如果你使用非this对象作为锁,那么它绕过这个问题和其他。

使用全局对象(如静态variables)进行locking。

由于争用,这导致非常糟糕的performance。

Honesly? 在java.util.concurrent出现之前,我经常遇到的最常见的问题就是我所说的“线程颠簸”:使用线程进行并发的应用程序,但是产生了太多的线程并最终导致抖动。