Skip to content

Latest commit

 

History

History
42 lines (31 loc) · 4.04 KB

第84条:不要依赖线程调度器 173dc36ea99048f5948b0a63aaff6ddf.md

File metadata and controls

42 lines (31 loc) · 4.04 KB

第84条:不要依赖线程调度器

当有多个线程可运行时,由线程调度器(thread scheduler)决定那些线程将会运行,以及运行多长时间。任何一个合理的操作系统在做出这样的决定时,都会努力做到公正,但是所采用的策略却大相径庭。因此,编写良好的程序不应该依赖于这种策略的细节。任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能是不可移植的。

编写出健壮、响应良好的、可移植的多线程引用程序,最好的办法是确保可运行线程的平均数量不明显多余处理器的数量。这使得线程调度器没有更多的选择:它只需要运行这些可运行的线程,直到它们不再可运行为止。即使在根本不同的线程调度器算法下,这些程序的行为也可能不会有很大的变化。注意可运行的线程数量并不等于线程的总数量,前者可能更多。在等待的线程并不是可运行的。

保持可运行线程数量尽可能少的主要方法是,让每个线程做些有意义的工作,然后等待更多有意义的工作。如果线程没有再做有意义的工作,就不应该运行。根据Executor Framework,这意味着适当地规定了线程池的大小,并且使任务保持适当得小,彼此独立。任务不应该太小,否则分配的开销也会影响性能。

线程不应该处于一致忙-等(busy-wait)的状态,即反复地检查一个共享对象,以等待某些事情的发生。除了使程序收到调度器的变化影响之外,忙-等这种做法也会极大地增加处理器的负担,降低了统一机器上的进程可以完成的有用的工作量。作为不应该做的一个极端方面例子,考虑下面这个CountDownLatch的不正当的重新实现:

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来”修正“该程序。你可能好不容易成功让程序能够工作,但这样得到的程序任然是不可移植的。同一个yield调用在一个JVM实现上能提高性能,而在另一个JVM实现上却有可能会更差,在第三个JVM实现上则可能没有影响。Thread.yield没有可测试的语义。更好的解决办法是重新构造应用程序,以减少可并发运行的线程数量。

有一种相关的方法是调整线程优先级,也可以算是一条建议。线程优先级是Java平台上最不可移植的特性了。通过调整某些线程的优先级来改善应用程序的响应能力,这样做并非不合理,却是不必要的,也是不可移植的。通过调整线程的优先级来解决严重性的活性问题是不合理的饿。在你找到并修正地城的真正原因之前,这个问题可能会再次出现。

总而言之,不要让引用程序的正确性依赖线程调度器。否则,得到的应用程序将即不健壮,也不具有可移植性。同样,不要依赖Thread.yield或者线程优先级。这些机制都只是影响到调度器。线程优先级可以用来提供一个已经能够正常工作的程序的服务质量,但永远不应该用来”修正“一个原本不能工作的程序。