从Java并发API中的线程池中选择线程的背后逻辑是什么?

时间:2018-10-05 15:00:07

标签: java multithreading concurrency threadpool java.util.concurrent

Java concurrent API提供了许多有用的库和功能,这些功能使使用异步和多线程控制流变得更加容易。其中的一项功能是Thread Pools

今天早些时候,我正在尝试并发API,并注意到一些有关从线程池中选择线程的奇怪模式。他们让我想知道线程选择背后的逻辑是什么。下面是一些示例代码,也许可以为您提供我正在谈论的示例。当程序记录每个[时,您会注意到线程名中的模式(在] tick内部)。但是,该模式可能不会在64位Windows 10计算机上的Oracle JDK 1.8.0_161之外的JDK构建中出现。

无论如何,我的问题与任何巧合模式无关,而是关于从线程池中选择线程的过程。该模式让我相信它不是完全随机的,那么此选择背后的逻辑是什么?谢谢。 :)

public static void main(String[] args)
{
    // create a ScheduledExecutorService with a Thread Pool of 7 threads
    ScheduledExecutorService ses = Executors.newScheduledThreadPool(7);
    log("go");
    // starts a timer of 30 seconds, shutting down ses afterwards
    ses.schedule(() -> call(ses), 30, TimeUnit.SECONDS);
    // starts the ticker
    ses.schedule(() -> tick(ses, 1), 1, TimeUnit.SECONDS);
}

// ticks once per second, logging the current tick counter. (i.e, counts
// by 1 each second) ticking ends when ses is shutdown.
public static void tick(ScheduledExecutorService ses, int count)
{
    if (!ses.isShutdown())
    {
        log("tick %d", count);
        ses.schedule(() -> tick(ses, count + 1), 1, TimeUnit.SECONDS);
    }
}

// called when it's time to shutdown ses.
public static void call(ScheduledExecutorService ses)
{
    log("done");
    ses.shutdown();
}

// formats and logs the given message alongside a timestamp and the name
// of the executing thread
public static void log(String s, Object...args)
{
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    String time = sdf.format(new Date(System.currentTimeMillis()));
    String thread = Thread.currentThread().getName();
    String message = String.format(s, args);
    String log = String.format("%s [%s] - %s", time, thread, message);
    System.out.println(log);
}

1 个答案:

答案 0 :(得分:0)

您创建的ScheduledThreadPool内部有一个DelayQueue。提交任务时,它将始终创建一个新的工作线程,该线程将继续从DelayQueue接收任务。

提交任务时的代码,新线程继续执行任务

//getTask() method calls DelayQueue#take()
while (task != null || (task = getTask()) != null) {
       ......
       task.run();
       ......
}

如果take()方法返回零,则getDelay方法将返回一个任务,否则工作线程将变为等待倒计时结束。当时间到时,它将唤醒工作线程以获取任务。 。

DelayQueue#take()的代码

/**
 * Retrieves and removes the head of this queue, waiting if necessary
 * until an element with an expired delay is available on this queue.
 *
 * @return the head of this queue
 * @throws InterruptedException {@inheritDoc}
 */
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    return q.poll();
                first = null; // don't retain ref while waiting
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

当您提交7个任务时,有7个工作线程正在等待被唤醒。 因此线程选择取决于唤醒策略。通常,它是根据等待队列的顺序来唤醒的。