当许多线程可以运行时,线程调度程序决定哪些线程可以运行以及运行多长时间。任何合理的操作系统都会尝试公平地做出这个决定,但是策略可能会有所不同.因此,编写良好的程序不应该依赖于此策略的细节。任何依赖线程调度程序来保证正确性或性能的程序都可能是不可移植的。
编写健壮、响应快、可移植程序的最佳方法是确保可运行线程的平均数量不显著大于处理器的数量。这使得线程调度程序几乎没有选择:它只是简单地运行可运行线程,直到它们不再可运行为止。即使在完全不同的线程调度策略下,程序的行为也没有太大的变化。注意,可运行线程的数量与线程总数不相同,后者可能更高。正在等待的线程不可运行。
保持可运行线程数量低的主要技术是让每个线程做一些有用的工作,然后等待更多的工作。如果线程没有做有用的工作,它们就不应该运行。在 Executor Framework方面 ( item80 ),这意味着适当调整线程池的大小[Goetz06, 8.2],保持任务短,但不要太短,否则分派开销会损害性能。
线程不应该忙于等待,反复检查一个共享对象,等待它的状态发生变化。除了使程序容易受到线程调度程序变化无常的影响外,繁忙等待还大大增加了处理器的负载,减少了其他人可以完成的有用工作的数量。作为不应该做什么的极端例子,考虑一下CountDownLatch的反常重新实现:
image.png image.png
在我的机器上,SlowCountDownLatch大约比Java慢十倍
当1000个线程等待一个锁存器时,倒计时downlatch。虽然这个例子看起来有点牵强,但是看到一个或多个线程不必要地可运行的系统并不少见。性能和可移植性可能会受到影响。
当一个程序因为某些线程相对于其他线程没有足够的CPU时间而几乎不能工作时,通过调用Thread.yield来抵制“修复”程序的诱惑。您可能会成功地使程序在某种程度上工作,但它不会是可移植的。在一个JVM实现上提高性能的相同的yield调用在第二个JVM实现上可能会使性能变差,而在第三个JVM实现上没有任何影响。Thread.yield没有可测试的语义.更好的做法是重构应用程序,以减少并发运行线程的数量。
一个相关的技术是调整线程优先级,类似的警告也适用于此技术。线程优先级是Java中最不可移植的特性之一。通过调整几个线程优先级来调优应用程序的响应性并不是不合理的,但这很少是必要的,而且不可移植。试图通过调整线程优先级来解决严重的活性问题是不合理的。在找到并修复潜在原因之前,问题很可能会再次出现。
总之,不要依赖线程调度程序来判断程序的正确性。生成的程序既不健壮也不可移植。因此,不要依赖Thread.yield或线程优先级。这些工具只是对调度程序的提示,线程优先级可以少量地用于提高已经运行的程序的服务质量,但绝不应该用于“修复”几乎不能工作的程序.
本文写于2019.7.23,历时1天
网友评论