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

我正在编写一个Java程序的中途,而我正在调试比我想要处理的并发问题更多的并发问题。

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

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

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

“所有并发问题都归结为协调对可变状态的访问。可变状态越少,确保线程安全就越容易。” – 实践中的Java并发

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

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

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

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

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

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

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

阅读并发性,或者更好的是,如果你还在大学,那就读并发编程的研究生课程。 请参阅Java教程:课程:并发 。 一本着名的Java并发书是Java Concurrency in Practice 。 Java已经内置到框架中以处理并发问题,包括并发集合和synchronized方法 。

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

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

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

编辑:

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

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

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

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

还要记住,如果列表是共享状态,其内容也是如此,因此如果您打算通过复制列表来绕过锁定,请确保执行深层复制,或者certificate列表中包含的对象本身是线程安全的。

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

 List items = new ArrayList(); //... 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 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 } } 

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

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

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

  1. 尝试使用java.util.concurrent包中的集合,甚至是来自Google Collections的更好的不可变集合。
  2. 阅读有关使用同步块的信息

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

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