如何在开始编码之前处理并发

时间:2010-01-29 22:52:01

标签: java concurrency multithreading project-planning

我正在编写Java程序的过程中,我正处于调试并发问题的阶段,而不是我想要处理的问题。

我不得不问:在精神上设置你的程序时,你如何处理并发问题?在我的情况下,这是一个相对简单的游戏,但线程问题不断出现 - 任何快速修复几乎肯定会导致一个新的问题。

用非常笼统的术语来说,在决定我的应用程序如何“流动”而我的所有线程都没有结束时,我应该使用哪些技巧?

9 个答案:

答案 0 :(得分:6)

并发归结为管理共享状态。

  

“所有并发问题归结为   协调对可变状态的访问。   可变性越小,越容易   是为了确保线程安全。“    - Java Concurrency in Practice

所以你必须问自己的问题是:

  • 我的应用程序需要什么固有的共享数据?
  • 什么时候线程可以处理数据的快照,也就是说,它会在共享数据的克隆上暂时起作用?
  • 我是否可以识别已知的模式并使用更高级别的抽象而不是低级锁定和线程协调,例如队列,遗嘱执行人等?
  • 考虑一个全局锁定方案,以避免死锁并获得一致的锁定

管理共享状态的最简单方法是序列化每个操作。然而,这种粗粒度的方法导致高锁争用和差的性能。管理并发可以看作是一种优化练习,您可以尝试减少争用。所以后续问题是:

  • 最简单的方法是什么?
  • 我可以做出哪些简单的选择来减少争用(可能是细粒度锁定)并提高性能而不会使解决方案过于复杂?
  • 我什么时候过于细粒度,也就是说,引入的复杂性不值得获得性能提升?

许多减少争用的方法依赖于某种形式的权衡在强制执行正确行为和减少争用的可行性之间。

  • 我可以在哪里放松一些约束并接受有时候东西不会100%正确(例如柜台)?
  • 我是否可以乐观并且只有在发生并发修改时处理冲突(例如使用时间戳和重试逻辑 - 这就是TM所做的事情)?

请注意,我从未参与游戏,只在企业应用程序的服务器端部分工作。我可以想象它可能会有很大不同。

答案 1 :(得分:5)

我尽可能使用不可变数据结构。关于我唯一一次使用可变结构的时候,就像我需要一个可以节省大量工作的库一样。即使这样,我也尝试将该库封装在一个不可变的结构中。如果事情无法改变,那就不用担心了。

我应该补充一点,关于你未来的努力要记住的一些事情是STM和演员模型。这两种并发方法都显示出非常好的进展。虽然每个都有一些开销,但取决于程序的性质可能不是问题。

编辑:

以下是您可以在下一个项目中使用的一些库的一些链接。有Deuce STM顾名思义是Java的STM实现。然后是ActorFoundry,顾名思义是Java的Actor模型。但是,我不禁用Scala使用内置的Actor模型制作插件。

答案 2 :(得分:5)

阅读并发性,或者更好的是,如果你还在大学,那就参加并行编程的研究生课程。见The Java Tutorials: Lesson: Concurrency。一本着名的Java并发书是Java Concurrency in Practice。 Java已经内置到框架中以处理并发问题,包括concurrent collectionssynchronized methods

Java Concurrency in Practice http://ecx.images-amazon.com/images/I/51Hx%2Bg4Q6QL._BO2,204,203,200-76_AA240_SH20_OU01_.jpg

答案 3 :(得分:2)

您拥有的线程越少,他们共享的状态越小,他们在此共享状态下的交互模式越简单,您的生活就会越简单。

你说Lists正在抛出ConcurrentModificationException。我认为你的列表是由单独的线程攻击的。所以你要问自己的第一件事是这是否必要。第二个线程是否无法对列表的副本进行操作?

如果线程确实需要同时访问列表,则在整个遍历期间锁定列表可能是一个选项(如果通过除此之外的任何其他方式修改列表,则迭代器无效迭代器)。当然,如果您在遍历列表时执行其他操作,则此遍历可能需要很长时间,而锁定其他线程可能会威胁到系统的活跃性。

另请注意,如果列表是共享状态,其内容也是如此,因此如果您打算通过复制列表来绕过锁定,请确保执行深层复制,或者证明列表中包含的对象是他们自己的线程安全。

答案 4 :(得分:2)

对于您提到的ConcurrentModificationExceptions,您的应用程序的多线程特性可能是一个红色的鲱鱼:还有其他方法可以获得不一定涉及多个线程的ConcurrentModificationException。 请考虑以下事项:

List<Item> items = new ArrayList<Item>();

//... some code adding items to the list

for (Item item : items) {
    if(item.isTheOneIWantToRemove()) {
        items.remove(item); //This will result in a ConcurrentModificationException
    }
}

使用迭代器或增加索引值将for循环更改为循环可以解决问题:

for (Iterator<String> it = items.iterator(); it.hasNext();) {
    if(item.isTheOneIWantToRemove()) {
        it.remove(); //No exception thrown
    }
}

for (int i = 0; i < items.size(); i++) {
    if(item.isTheOneIWantToRemove()) {
        items.remove(items.get(i)); //No exception thrown
    }
}

答案 5 :(得分:1)

从设计的角度来看,我发现绘制序列图很有用,其中每个线程的动作都是彩色编码的(也就是说,每个线程都有自己的颜色)。以这种方式使用颜色可能是序列图的非标准使用,但它有助于概述线程如何以及在何处进行交互。

正如其他人所提到的那样,将设计中的线程数量减少到绝对最小值,它需要正常工作也会有很大帮助。

答案 6 :(得分:0)

这取决于你的线程做什么。通常程序有一个主线程,可以思考和工作线程执行并行任务(定时器,在GUI上处理长计算等)但您的应用程序可能不同 - 这取决于您的设计。你用什么线程?您有什么锁来保护共享数据结构?如果您使用多个锁,您是否有一个锁定以防止死锁的订单?

答案 7 :(得分:0)

  1. 尝试使用java.util.concurrent包中的集合,或者使用来自Google Collections的更好的不可变集合。
  2. 了解使用synchronized块

答案 8 :(得分:-1)

在设计应用程序时,我建议考虑共享哪些程序资源。本文深入介绍了各种线程之间如何共享各种Java资源:

http://javatip.com/2010/07/core-java/concurrency/thread-safe-without-synchronization