当我学习Java从一些20年的基础,帕斯卡,COBOL和C的过程式编程的背景来了,我当时认为它是最难的事情是包裹我的头周围的OOP术语和概念的时间.现在我带大约8年的稳固的Java,我得出的结论是,关于Java编程和类似的语言,如C#单最难的事情是多线程/并发方面.
编写可靠且可扩展的多线程应用程序非常困难!随着处理器变得"更宽"而不是更快的趋势,它正在迅速变得非常关键.
当然,最困难的领域是控制线程之间的交互以及由此产生的错误:死锁,竞争条件,陈旧数据和延迟.
所以我的问题是:您采用什么方法或方法来生成安全的并发代码,同时减少死锁,延迟和其他问题的可能性?我提出了一种有点非传统的方法,但在几个大型应用程序中工作得非常好,我将在这个问题的详细答案中分享.
这不仅适用于Java,也适用于一般的线程编程.我发现自己只是遵循以下准则来避免大多数并发和延迟问题:
1 /让每个线程运行自己的生命周期(即决定何时死亡).它可以从外部提示(比如一个标志变量),但它完全负责.
2 /让所有线程以相同的顺序分配和释放资源 - 这可以保证不会发生死锁.
3 /尽可能在最短的时间内锁定资源.
4 /通过数据本身传递数据的责任 - 一旦您通知线程数据是要处理的数据,请不要管它,直到责任交还给您.
刚才有许多技术进入公众意识(如:过去几年).一个重要的是演员.这是Erlang首先引入网格的东西,但是由Scala等新语言(JVM上的演员)推进.虽然演员确实没有解决所有问题,但他们确实更容易推理您的代码并找出问题所在.它们还使设计并行算法变得更加简单,因为它们强制您使用连续传递共享可变状态的方式.
Fork/Join是你应该看的东西,特别是如果你在JVM上.Doug Lea撰写了关于该主题的开创性论文,但许多研究人员多年来一直在讨论它.据我了解,Doug Lea的参考框架计划包含在Java 7中.
在略微侵入性较低的级别上,通常简化多线程应用程序所需的唯一步骤只是降低锁定的复杂性.细粒度锁定(Java 5风格)非常适合吞吐量,但很难做到正确.锁定的一种替代方法是通过Clojure获得一些牵引力的是软件事务存储器(STM).这基本上与传统锁定相反,因为它是乐观的而不是悲观的.首先假设您不会发生任何冲突,然后允许框架在问题出现时解决问题.数据库通常以这种方式工作.它对于具有低冲突率的系统的吞吐量非常有用,但最大的好处在于算法的逻辑组件化.您可以将危险代码包装在事务中,让框架弄清楚其余部分,而不是任意地将锁(或一系列锁)与某些数据相关联.您甚至可以通过GHC的STM monad或我的实验性Scala STM获得相当多的编译时间检查.
构建并发应用程序有很多新选项,您选择的应用程序在很大程度上取决于您的专业知识,语言以及您尝试建模的问题类型.作为一般规则,我认为演员与持久的,不可变的数据结构相结合是一个坚实的选择,但正如我所说,STM的侵入性稍差,有时可以产生更直接的改进.
尽可能避免在线程之间共享数据(复制所有内容).
在可能的情况下,永远不要锁定对外部对象的方法调用.
尽可能在最短的时间内保持锁定.
Java中的线程安全没有一个真正的答案.但是,至少有一本非常好的书:Java Concurrency in Practice.我经常参考它(尤其是我旅行时的在线Safari版本).
我强烈建议您仔细阅读本书.您可能会发现深入研究非常规方法的成本和收益.