什么是快速,等待通知或忙于等待Java?

时间:2014-07-25 05:43:12

标签: java multithreading

据我所知,使用busy-wait不是一个好的编程习惯,最好尽可能使用同步对象(wait-notify)。但是我想知道一个人是否准备好牺牲cpu周期,然后忙着等待更快或等待通知?

我假设wait-notify将涉及对同步对象的内部锁定,并且信号可能来自内核以唤醒线程,使得这种方法比忙等待更慢,其中人们可以连续检查条件直到满意为止。只要满足此条件(例如,布尔值== true),线程就可以从繁忙的等待中退出。根据我的理解,我感到很忙 - 等待应该更快。

如果我的论点是错误的,如果其他人可以分享他们的想法并纠正我,我将不胜感激。

4 个答案:

答案 0 :(得分:8)

实验表明,如果你等待并等待并通知(在我的硬件上,无论如何),你会更快地看到旗帜。 (详情如下。)差异是 非常非常非常小 ,所以这只适用于非常罕见的应用。例如,股票交易应用程序,公司可以获得他们可以获得的任何优势(争取尽可能在交易所附近找到他们的服务器,以便从交易所获得微网改进等)可能会认为差异是值得的。我也可以想象一些科学应用。

在绝大多数应用中,差异实际上没有任何区别。

但是,CPU发生的事情当然是其中一个核心硬件:

hardpeg

在影响盒子上的其他进程和数据中心的功耗方面,这是不好的。

所以:极度不情愿地使用,只有在真正重要的情况下使用。


数据(非常小的样本,但代码如下):

Busy Wait:       10631  12350  15278
Wait and Notify: 87299 120964 107204
Delta:           76668 108614  91926

时间是 nano 秒。十亿分之一秒。上面的平均增量为92403ns(0.092402667毫秒,0.000092403秒)。

BusyWait.java

public class BusyWait {

    private static class Shared {
        public long setAt;
        public long seenAt;
        public volatile boolean flag = false;
    }

    public static void main(String[] args) {
        final Shared shared = new Shared();
        Thread notifier = new Thread(new Runnable() {
            public void run() {
                System.out.println("Running");
                try {
                    Thread.sleep(500);
                    System.out.println("Setting flag");
                    shared.setAt = System.nanoTime();
                    shared.flag = true;
                }
                catch (Exception e) {
                }
            }
        });
        notifier.start();
        while (!shared.flag) {
        }
        shared.seenAt = System.nanoTime();
        System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
    }
}

WaitAndNotify.java

public class WaitAndNotify {

    private static class Shared {
        public long setAt;
        public long seenAt;
        public boolean flag = false;
    }

    public static void main(String[] args) {
        (new WaitAndNotify()).test();
    }
    private void test() {
        final Shared shared = new Shared();
        final WaitAndNotify instance = this;
        Thread notifier = new Thread(new Runnable() {
            public void run() {
                System.out.println("Running");
                try {
                    Thread.sleep(500);
                    System.out.println("Setting flag");
                    shared.setAt = System.nanoTime();
                    shared.flag = true;
                    synchronized (instance) {
                        instance.notify();
                    }
                }
                catch (Exception e) {
                }
            }
        });
        notifier.start();
        while (!shared.flag) {
            try {
                synchronized (this) {
                    wait();
                }
            }
            catch (InterruptedException ie) {
            }
        }
        shared.seenAt = System.nanoTime();
        System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
    }
}

答案 1 :(得分:2)

一个人准备好在繁忙的等待中牺牲CPU周期,因为它更快。 忙碌等待是实时低延迟应用的示例。

有一个名为lmax disruptor的框架是为伦敦证券交易所建立的,其中一个锁定策略是忙等待,这就是他们使用它的方式。

为了达到超快速度,在通知锁定的情况下,最好浪费cpu周期,确保等待时间。

你对所有其他的东西都是正确的,如果你稍微谈论破坏者并阅读他们的论文,你会得到更多的澄清。关于高性能和低延迟,有太多事情要说。

要看的一个好博客是Mechanical Sympathy

答案 2 :(得分:0)

忙碌等待比正常等待通知更快。

  1. 但你为什么要等?因为一个生产者或其他线程会做一些工作,然后设置一个条件(或通知),以便你实际上可以退出忙/等待循环。 现在假设你的制作人正在做一些繁重的任务,那么你实际上是通过忙碌等待(主要是在单处理器系统中)来吃掉它的cpu周期,这反过来可能会使你的系统整体变慢。

  2. 所以现在你应该使用忙等待。 正如Claudio所说,它主要用于低延迟系统。 但仍然不能盲目使用它。当你的制片人使用忙等待 正以稳定的速度生产。 如果您的制作人以可变利率生成项目(通常由泊松分布证明),那么您应该使用等待通知。

  3. 通常情况下,高吞吐量和低延迟系统的最佳折衷方案是使用 做一段忙碌的等待,然后去等待()。 如果您的系统需要超低持续性,那么您可以进行许多优化 其中一个可能是Busy-Wait。 但它不应该是每个线程都忙着等待。确保只有一些消费者 可能在N / 2左右消费者正忙着等待N是您的核心数量 系统。浪费CPU周期可能会影响系统的整体性能和响应能力。 对于您的参考:Even Normal ReentrantLock及其变体适用于这些策略。 I,E 即当线程调用lock.lock()时,它会尝试在进入队列之前获取锁定两次并等待锁定被释放。对于低延迟系统,您甚至可以为进入队列之前尝试10次以上的特定场景定义自己的锁(它们将是所谓的自旋锁的变体)

答案 3 :(得分:0)

取决于。有几种情况:

情景A

如果在'busy-wait'中等待的是硬件操作(例如,从硬盘读取扇区到内存):

1)硬件将执行操作。

2)驱动程序将启动中断。

3)操作将暂停实际过程(忙碌等待过程),保存任何CPU注册中断将在其处理中覆盖的实际值。

4)中断将被处理,修改任何指示数据可用的标志(如果是磁盘读取)。

5)将恢复任何覆盖寄存器。

6)您的流程将继续其流程。只是在下一次迭代中,它将调用循环的conthe条件。例如,如果忙等待是:

while( !fileReady() ){
    ...
}

fileReady()方法将是一个内部检查是否设置了具体标志(在4中修改的标志)的方法。 7)所以在下一次迭代中,循环将进入并执行操作。

请记住,如果有另一个进程正在运行(操作系统进程,其他程序),它们会将您的进程置于进程尾。此外,操作系统可以决定,鉴于您的进程已经使用了他可以使用的所有CPU周期(它花费了它的量子),它将具有较少的优先级,其他进程进入休眠状态(而不是使用忙等待方法)需要等待一定的条件。

结论。如果没有其他外部进程在同一个内核/ CPU中运行(非常不可能),它会更快。


情景B

另一方面,如果忙方法正在等待另一个进程结束(或将任何变量设置为某个值,则忙等待将会更慢。

1)busy方法将在CPU中运行。由于其他进程没有运行,因此条件不能更改,因此busy方法将一直运行,直到CPU决定为另一个进程提供CPU时间。

2)另一个进程将运行。如果这个过程花费时间而没有实现繁忙等待过程需要的混乱,那么转到1),否则继续3)

3)另一个(不是忙等待)进程仍会运行一段时间,直到cpu决定新的进程更改。

4)busy-method将再次运行,但现在条件已满足,因此现在已完成操作。

结论:它较慢,我们也会减慢整个过程。


情景C

如果我们的方案与B相同,但有多个核心(每个核心中有一个进程),该怎么办?

首先,请记住,即使您有一个具有多个内核的CPU,您的程序也可能不允许使用多个内核。也许有一些操作系统或其他程序使用它们。

其次,它不值这个价格。请记住,该过程必须通信以允许忙碌等待发现条件满足。这通常可以通过'final'变量来完成,因此每次评估条件时都需要在同步块中输入(在进入循环之前无法锁定并且没有解锁它,因为在其他过程中)将无法更改变量。所以你需要这样的东西:

boolean exit = false;
while( exit==false ){
    synchronize(var){
        if(var = CONDITIONS_MEET)
            exit=true;
    }
}
//operations...

}

然而,wait-notify会做类似的事情,并且更有效率(在语言级别),而不会浪费CPU周期,并使用良好的prectices !!

结论:你的生活变得复杂,不太可能会更快(非常非常不可能)。


最终结论:只有当您了解操作系统的具体细节以及运行程序的环境时,才会处于一个非常简单的场景中,您可以考虑忙等待的方法

我希望这会解决你的问题。不要犹豫,问一下是不是很清楚。