ITEM 84: 不要依赖线程调度

ITEM 84: DON’T DEPEND ON THE THREAD SCHEDULER
  当许多线程是可运行的时,线程调度程序决定哪些线程可以运行以及运行多长时间。任何合理的操作系统都会试着公平地做出决定,但是策略可能会有所不同。因此,编写良好的程序不应该依赖于此策略的细节。任何依赖于线程调度程序的正确性或性能的程序都可能是不可移植的。
  编写健壮、响应性强、可移植程序的最佳方法是确保可运行线程的平均数量不会明显大于处理器的数量。这让线程调度程序别无选择:它只是运行可运行的线程,直到它们不再可运行为止。即使在完全不同的线程调度策略下,程序的行为也不会有太多变化。请注意,可运行线程的数量与线程总数并不相同,线程总数可以更高。等待的线程是不能运行的。
  保持可运行线程数量低的主要技术是让每个线程做一些有用的工作,然后等待更多的工作。如果线程没有做有用的工作,它们就不应该运行。就 Executor 框架而言(item 80),这意味着适当调整线程池大小[Goetz06, 8.2],保持任务简短,但不要太短,否则分配开销会损害性能。
  线程不应该忙碌等待,反复检查共享对象,等待其状态改变。除了使程序容易受到线程调度器的攻击之外,忙碌等待还大大增加了处理器的负载,减少了其他人可以完成的有用工作的数量。作为一个不应该做的极端例子,考虑一下 CountDownLatch 的一个不恰当的实现:

// Awful CountDownLatch implementation - busy-waits incessantly!
public class SlowCountDownLatch {
  private int count;
  public SlowCountDownLatch(int count) { 
    if (count < 0)
      throw new IllegalArgumentException(count + " < 0"); 
    this.count = count;
  }
  public void await() {
    while (true) { 
      synchronized(this) {
        if (count == 0) 
          return;
      } 
    }
  }
  public synchronized void countDown() { 
    if (count != 0)
      count--; 
  }
}

  在我的机器上,当1000个线程等待一个闩锁时,SlowCountDownLatch 比 Java 的countdownlatch 慢10倍。虽然这个例子看起来有点牵强,但是我们经常可以看到系统中有一个或多个线程是不必要的。性能和可移植性可能会受到影响。
  当遇到因为某些线程相对于其他线程没有获得足够的 CPU 时间而几乎不能工作的程序时,请抵制通过调用 Thread.yield 来“修复”程序的诱惑。您可能在某种程度上成功地使程序工作,但它是不可移植的。提高一个 JVM 实现的性能的相同的 yield 调用可能会使第二个 JVM 实现的性能更差,并且对第三个 JVM 实现没有影响。Thread.yield 没有可测试的语义。更好的做法是重新构造应用程序,以减少可并发运行的线程数量。
  一个相关的技术(类似的警告也适用) 是调整线程优先级。线程优先级是 Java 中可移植性最差的特性之一。通过调整几个线程的优先级来调整应用程序的响应性并不是不合理的,但是这种调整很少是必要的,而且是不可移植的。试图通过调整线程优先级来解决严重的活性问题是不合理的。问题很可能会再次出现,直到您找到并解决了潜在的原因。
  总之,程序的正确性不要依赖于线程调度程序。得到的程序既不健壮也不能移植。因此,不要依赖于 Thread.yield 或线程优先级。这些工具只是对调度器的暗示。线程优先级可以用来改善已经运行的程序的服务质量,但绝不应该用来“修复”几乎不能工作的程序。

你可能感兴趣的:(ITEM 84: 不要依赖线程调度)