Java,线程和优先级

时间:2018-07-07 22:01:51

标签: java multithreading scheduling

我正在解决一个本来应该很简单的问题,但是我做起来并不容易。

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

尽管我的第一个任务是为每个功能使用一个单独的线程池队列,但我将F1池设置为具有8个线程,而F2池只有2个线程。

在我的期望中,Linux将为每个线程分配相当的时间份额,因此F1将有8个量子,而F2将只有2个量子。如果没有F1请求,则F2池可以将每个量子分配给自己,同样如此如果F2没有请求,则为F1。

但是,该程序却不是这样,如果我收到大量的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;
    }
}

1 个答案:

答案 0 :(得分:1)

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

线程争用

使用BlockingQueue需要付出一定的代价-生产者或消费者之间争执的每个写操作(投入和取出)都是锁。您的“ A池”有9个线程争用queueA的写锁(1个生产者,8个使用者),而您“ B池”有3个线程争用queueB的写锁(1个生产者, 2个消费者)。

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

线程调度

如评论中所述,您受JVM如何调度线程的支配。

如果Java线程调度在CPU上使用了完全公平的时间分配,那么您可能会发现同一池中每个线程的消耗计数非常接近。您可能已经注意到它们不是-我对您的代码(略作修改)的运行有时会给我计数300K或更多的线程数。

当每个受CPU绑定的线程有足够的CPU内核时(通常在示例代码中有12个),您通常会获得更好的性能,但是在许多情况下,尤其是面对线程争用时,这远非理想。

你能做什么?

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

不是很有趣吗? :)