【问题标题】:Java, Thread and PrioritiesJava、线程和优先级
【发布时间】:2018-07-07 22:01:51
【问题描述】:

我正在解决一个问题,这应该很容易解决,但是我没有那么容易解决。

问题很简单:我有一个在 Linux/x86 上运行的 Java 程序,它可以执行两个基本功能 F1 和 F2。我想将 F1 设置为具有更高的优先级,即使 F2 不时执行是必须的,即队列中有 F1 请求的事实不能让 F2 请求永远等待。

虽然我的第一个队列只是为每个功能设置一个带有线程池的单独队列,但我将 F1 池设置为有 8 个线程,而 F2 池只有 2 个线程。

在我的预期中,Linux 会为每个线程提供相当的时间共享,因此 F1 将有 8 个量子,而 F2 将只有 2 个。如果没有 F1 请求,F2 池可以将每个量子都分配给它自己,这应该也是如此用于 F1 以防 F2 没有请求。

但是,程序的行为并非如此,如果我收到一连串 F2 请求,而只有几个 F1 请求,则后者需要很长时间才能轮到它。

谈论 Oracle HotSpot/linux 调度是否有意义?或者它不应该发生,从我的角度来看,什么会指向一个实施错误?

PS:我已经阅读了有关 linux 调度的信息,似乎 SCHED_OTHER (TS) 为每个任务提供了时间共享,但是每次未执行准备好的任务时,它都会获得更大的时间量,如果这种情况发生在 F2池,这可能解释了上述行为。

感谢和问候。

下面有一个示例源代码。

package test;

import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * Created by giscardff on 08/07/18.
 */
public class TestThread {

    // Test Program
    public static void main(String args[]) throws Exception {

        // queues containing jobs to be done
        ArrayBlockingQueue<MyDTO> queueA = new ArrayBlockingQueue<>(100);
        ArrayBlockingQueue<MyDTO> queueB = new ArrayBlockingQueue<>(100);

        // create pool for functionality A
        for(int i = 1; i <= 8; i++){
            MyThread thread = new MyThread("ThreadA" + i, queueA);
            thread.start();
        }

        // create pool for functionality B
        for(int i = 1; i <= 2; i++){
            MyThread thread = new MyThread("ThreadB" + i, queueB);
            thread.start();
        }

        // create producer for A
        // it will take 100ms  between requests
        Producer producerA = new Producer(queueA, 0);
        producerA.start();

        // create producer for B
        // it will take 0ms between requests
        Producer producerB = new Producer(queueB, 0);
        producerB.start();

    }

}

/**
 * Just put a request into a queue
 */
class Producer extends Thread {

    private ArrayBlockingQueue<MyDTO> queue;
    private long sleep;

    public Producer(ArrayBlockingQueue<MyDTO> queue, long sleep){
        this.queue = queue;
        this.sleep = sleep;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if(sleep > 0)Thread.sleep(sleep);
                queue.put(new MyDTO());
            }
        }catch(Exception ex){}
    }
}

/**
 * Retrieve a request from a queue, calculate how long request took to
 * be received for each 1M requests
 */
class MyThread extends Thread {

    private ArrayBlockingQueue<MyDTO> queue;
    private long delay = 0;
    private int count = 0;

    public MyThread(String name, ArrayBlockingQueue<MyDTO> queue){
        super(name);
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                MyDTO input = queue.take();
                delay += System.currentTimeMillis() - Long.parseLong(input.getTime());
                if(++count % 1000 == 0){
                    System.out.printf("%s: %d\n", getName(), delay / 10);
                    count = 0;
                }
            }
        }catch(Exception ex){ex.printStackTrace();}
    }
}

/**
 * Just a DTO representing a request
 * NOTE: The time was set as String to force CPU to do something more than just math operations
 */
class MyDTO {
    private String time;
    public MyDTO(){
        this.time = "" + System.currentTimeMillis();
    }

    public String getTime() {
        return time;
    }
}

【问题讨论】:

  • 我认为您受制于操作系统调度程序以及 JVM 与它的交互方式。我不知道有任何 Java API 可以让您精确设置两个线程分配时间的确切时间量。
  • 您能发布管理线程的代码吗?任务是 IO 受限还是 CPU 受限?你是如何测量它们的?
  • 机器有几个核心?
  • 我认为解决此类问题的最佳方法是 PriorityQueue,或者可能是 ForkJoinPool,因为它的工作窃取算法。
  • 您的代码正在运行 SQL 语句?然后您就没有使用 CPU,因为它们大部分时间都在等待数据库服务器。您的性能测试毫无意义,因为您根本没有测试应用服务器的 CPU 调度。

标签: java multithreading scheduling


【解决方案1】:

您似乎遇到了一些问题。我将尝试总结它们并为前进的道路提供起点:

线程争用

使用BlockingQueue 是有代价的——每个写操作(put & take)都会在生产者或消费者之间进行锁竞争。您的“A 池”有 9 个线程争夺 queueA(1 个生产者,8 个消费者)的写锁,而您的“B 池”有 3 个线程争夺 queueB(1 个生产者,2 个消费者)的锁。

This related answer 提供了有关争用的更多详细信息。解决此问题的最简单方法是“使用更少的线程”或使用“无锁”机制来消除争用。

线程调度

正如 cmets 中所提到的,您完全取决于 JVM 如何调度您的线程。

如果 Java 线程调度在 CPU 上使用完全公平的时间份额,您可能会看到同一池中每个线程的消耗计数非常接近。您可能已经注意到它们不是 - 我运行您的(稍微修改过的)代码偶尔会给我一个跨线程的 300K 或更多计数分布。

当每个 CPU 绑定线程有足够的 CPU 内核(示例代码中有 12 个)时,您通常可以得到更好的结果,但在许多情况下它远非理想,尤其是在线程争用的情况下。

你能做什么?

  1. 构建您自己的公平逻辑 - 不要依赖 JVM 线程调度程序来实现公平,因为它不会。
    • 对于您的情况,一个简单的想法是保留两个队列,但使用单个池来处理这两个队列 - 使用循环或 Math.random()(即:if (rand &lt; 0.8) { queueA.poll();})来确定从哪个队列轮询。 注意 - 使用poll,这样您就可以轻松处理队列为空而不阻塞的情况。
  2. 用在您的硬件上运行的 CPU 绑定线程数进行实验。根据我对上述 (1) 的建议,您甚至可以让一个工作线程公平地处理两个队列。请记住,争用相同资源的线程过多会减慢您的处理速度。

线程不是很有趣吗? :)

【讨论】:

  • 我用 LinkedBlockingQueue 做了一个测试,看起来更好的结果只是为了得到相同的结果,并在源代码中看到它们对头和尾都有一个独特的锁:P 支持 ArraryBlockingQueue... 使用相同的测试PoolB=2/PoolA=5,PoolB 获得 2.6M/PoolA 获得 880K(几乎是 3 倍)与 PoolB=2/PoolA=5 相同的测试,PoolB 获得 5.2M/PoolA 获得 3M(73% 以上)与 PoolB 相同的测试=2/PoolA=2,PoolB 得到 11M/PoolA 得到 41M,wtf?是的,这一次 PoolA 几乎增加了 3 倍所以,我相信原始问题的答案肯定是更多线程并不意味着(肯定)更多分时
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2011-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多