【问题标题】:Java application stuck when using synchronized keywordJava 应用程序在使用 synchronized 关键字时卡住了
【发布时间】:2012-02-19 06:10:51
【问题描述】:

我有一个启动几个线程的类。每个线程(扩展线程)调用类 WH 的一个新实例,类 WH 有一个要在所有线程之间共享的变量。所以层次结构看起来像:

class S extends Thread {
....
....
  WH n = new WH(args);
....
....
}

现在 WH 类有一个要共享的变量,声明为:

private static volatile Integer size;

One of the functions tries to access size through Synchronized:
Synchronized (size) { // Program gets stuck at this line
 ... stuff ...
}

即使我只生成一个线程,它也会卡住。知道为什么会这样吗? (仅供参考-根据我的设计选择,我不想使用 AtomicInteger)

谢谢

【问题讨论】:

  • 好吧,你的设计选择现在不起作用,AtomicInteger 就是为这个确切的用例而设计的。共享状态很难正确执行,最好使用其他人设计、测试和证明的工具。祝你好运。
  • 我不能使用 AtomicInteger 因为我需要获取大小的值,检查它的条件,并根据条件增加或不增加。所以我必须做一个 get 然后可能会增加它。在那种情况下我仍然需要锁定。另外,如果我使用 ConcurrentHashMap 它也会卡住。
  • 尝试在Synchronized关键字中使用另一个锁,例如WH.class(如果没有在其他地方使用)。

标签: java multithreading concurrency synchronized


【解决方案1】:

您的问题是锁定 Non-Final 变量引用具有无用的语义

任何时候你看到做synchronized(var);var的东西是一个实例或静态变量并且没有标记final,这是一个错误,因为任何事情都可能出现并做一个var = new Thing();,现在那里至少有 2 个线程可以同时对该块进行操作,这是一个逻辑错误,没有例外。每个 Java lint 样式检查器都会将此标记为严重错误,仅仅因为编译器没有捕获到它并不意味着它在任何情况下都有任何用处。

在这种情况下,您通过更改不可变 Integer 类的值来暴露这些无用的语义。

您的 Integer 变量 size 不是最终变量,而是 Immutable,这意味着每次您更改它时,您必须更改引用到代表新值的新对象,并且每个线程将获得一个新的不同的引用来锁定。因此没有锁定

使用private static final AtomicInteger size = new AtomicInteger();

然后您可以synchronize(size);,因为size 现在是final,您可以对其进行变异并获得预期和正确的语义。

或者您可以synchronize(some_other_final_reference); 并使用常规的int,只要同步的引用是final 并且可以在需要获取它的句柄的任何线程的范围内,它就可以工作。

就我个人而言,我会使用AtomicInteger,这样更有凝聚力,您可以锁定您不希望通过任何其他线程更改的内容、自我记录和明确的意图。

【讨论】:

  • 这似乎回答了这个问题,“为什么我的 synchronized 关键字不阻止其他线程?”这与这个问题相反。我不知道为什么这段代码虽然被破坏了,但会导致单个线程阻塞——除非其他一些线程也锁定了Integer,由于valueOf缓存而恰好是同一个实例。
  • @yshavit 没有看到所有其他代码,这只是推测,但我猜想由于同步块内的某些东西或@987654340 的副作用而发生了某种竞争条件@ 关键字,它只是将该行报告为最后一个执行位置。套用我的合气道老师“做并发很容易,正确地做并发是困难
  • 做一个 synchronized(var);并且 var 没有标记为 final,这是一个错误 ==> 不,就是这样。 Java 不允许你编译它,否则会抛出 RuntimeExceptionError 如果它确实是一个错误。我同意的是,很难找到一个用例来证明它的合理性并把它做对。我只是认为通过撒谎来阻止不良和潜在有害的习惯(例如在非最终字段上同步)是非常糟糕的。只要告诉它是邪恶的,这就足够了,并且不会制造虚假信息。
  • @Bruno 这是一个逻辑错误句号,没有一个语义上的理由可以潜在地和不确定地给每个不同的线程一个不同的锁来锁定应该受保护的资源。您可以随心所欲地争论,但简单明了,这在每种情况下都是逻辑错误。 Java 的每个 lint 程序在每种情况下都将其标记为严重错误。
  • @JarrodRoberson,您太狭隘了:您认为在除最终字段之外的任何其他内容上同步的目的是造成麻烦。如果您看不出它有什么用处,您是否至少同意我的观点,即开发 Apache Tomcat 的人似乎知道他们在做什么?看看Tomcat的7.0.25 SingleSignOn是如何实现的(synchronized(lookup(ssoId)) {...})。好吧,也许他们不知道自己在做什么。然后让我们假设开发 Google Guava 的人:看看 Guava 的 11.0.1 LocalCache.java()。
【解决方案2】:

我不能使用 AtomicInteger,因为我需要获取 size 的值, 检查它的条件,并根据条件增加或不增加。 所以我必须做一个 get 然后可能会增加它。我还需要 在这种情况下锁定。

我相信您所描述的是 AtomicInteger 绝对可以通过compareAndSet() 方法在不锁定的情况下做的事情,不是吗?虽然唯一受支持的测试是平等,但也许这对你不起作用。

另外,如果您计划在变量上同步,则无需同时设置为volatile

【讨论】:

    最近更新 更多