Mastering Custom Thread Pools: Choosing the Right Queue in Java

This article walks through Java thread‑pool queue options—LinkedBlockingQueue, SynchronousQueue, LinkedBlockingDeque, and PriorityBlockingQueue—explaining their characteristics, demonstrating code examples, and showing how to build multi‑priority executors with concrete output analysis.

FunTester
FunTester
FunTester
Mastering Custom Thread Pools: Choosing the Right Queue in Java

After covering basic thread‑pool creation parameters, the article focuses on the BlockingQueue<Runnable> used as a thread‑pool waiting queue and examines three concrete implementations provided by the Java SDK.

LinkedBlockingQueue

LinkedBlockingQueue is a FIFO, thread‑safe, blocking queue that can be bounded or unbounded. If no capacity is set, it defaults to java.lang.Integer#MAX_VALUE. Its key properties are:

FIFO ordering.

Thread‑safety for concurrent producers and consumers.

Blocking put and take operations.

Optional capacity limit.

SynchronousQueue

SynchronousQueue has zero capacity; each offer must wait for a consumer take. It is used by Executors#newCachedThreadPool. When a task is offered, SynchronousQueue#offer(E) returns false, causing the pool to invoke ThreadPoolExecutor#addWorker and create a new thread.

LinkedBlockingDeque

LinkedBlockingDeque is a double‑ended blocking queue based on a linked list. It supports offerFirst / offerLast and pollFirst / pollLast, allowing tasks to be inserted at the head or tail. The following demo inserts the last submitted task at the head of the queue, giving it higher execution priority.

package org.funtester.performance.books.chapter01.section4;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 双端列表在线程池应用功能示例
 */
public class DueueDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(10)); // 创建线程池,使用双端列表
        for (int i = 0; i < 4; i++) { // 提交4个任务
            int index = i; // 任务索引
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(1000); // 模拟任务执行,睡眠1秒
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "  " + System.currentTimeMillis() + "  " + index + "  执行任务");
            });
            if (i == 3) { // 第4个任务插入到队列头部
                LinkedBlockingDeque<Runnable> queue = (LinkedBlockingDeque<Runnable>) executor.getQueue();
                queue.offerFirst(thread); // 将任务插入到队列头部
            } else {
                executor.execute(thread); // 提交任务
            }
            System.out.println(Thread.currentThread().getName() + "  " + System.currentTimeMillis() + "  " + index + "  提交任务");
        }
        executor.shutdown(); // 关闭线程池
    }
}

Running the demo prints the following console output, confirming that the fourth task (inserted at the head) executes immediately after the first task finishes:

main  1713000268720  0  提交任务
main  1713000268720  1  提交任务
main  1713000268720  2  提交任务
main  1713000268720  3  提交任务
pool-1-thread-1  1713000269721  0  执行任务
pool-1-thread-1  1713000270722  3  执行任务
pool-1-thread-1  1713000271723  1  执行任务
pool-1-thread-1  1713000272724  2  执行任务

The article notes that while this head‑insertion trick provides coarse‑grained priority, it becomes unwieldy with many priority levels. Java also offers java.util.PriorityQueue (non‑blocking) and java.util.concurrent.PriorityBlockingQueue, the latter being a thread‑safe blocking priority queue.

PriorityBlockingQueue

Compared with LinkedBlockingQueue, PriorityBlockingQueue adds:

Priority ordering based on element priority.

Unbounded capacity that grows dynamically.

To use it for a multi‑priority thread pool, tasks must implement Comparable or a custom Comparator. The article defines an abstract class PriorityRunnable that implements both Runnable and Comparable<PriorityRunnable>, storing an integer priorityLevel (smaller value = higher priority).

package org.funtester.performance.books.chapter01.section4;

/**
 * 优先级任务抽象类
 */
public abstract class PriorityRunnable implements Comparable<PriorityRunnable>, Runnable {
    int priorityLevel; // 优先等级,值越小优先级越高

    public PriorityRunnable(int priorityLevel) {
        this.priorityLevel = priorityLevel;
    }

    /**
     * 用于比较两个对象的优先级
     */
    @Override
    public int compareTo(PriorityRunnable o) {
        return this.priorityLevel - o.priorityLevel;
    }
}

The following demo creates a thread pool with a PriorityBlockingQueue<Runnable> and submits five tasks with decreasing priority values (5 down to 1). The first task (priority 5) runs immediately because the pool has no core threads; subsequent tasks are ordered by priority.

package org.funtester.performance.books.chapter01.section4;

import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 多优先级线程池使用示例
 */
public class PriorityTaskDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 2, 60L, TimeUnit.SECONDS,
                new PriorityBlockingQueue<Runnable>()); // 创建线程池,核心线程数0,最大线程数2
        for (int i = 0; i < 5; i++) { // 提交5个任务
            int priorityLevel = 5 - i; // 优先级递增
            executor.execute(new PriorityRunnable(priorityLevel) {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000); // 休眠1秒,模拟任务执行时间
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "  " + System.currentTimeMillis() + "  " + priorityLevel + "  执行任务");
                }
            });
            System.out.println(Thread.currentThread().getName() + "  " + System.currentTimeMillis() + "  " + priorityLevel + "  提交任务");
        }
        executor.shutdown(); // 关闭线程池
    }
}

Console output shows the tasks executing in priority order (except the first task, which runs immediately):

main  1713004306180  5  提交任务
main  1713004306180  4  提交任务
main  1713004306180  3  提交任务
main  1713004306180  2  提交任务
main  1713004306180  1  提交任务
pool-1-thread-1  1713004307181  5  执行任务
pool-1-thread-1  1713004308182  1  执行任务
pool-1-thread-1  1713004309183  2  执行任务
pool-1-thread-1  1713004310184  3  执行任务
pool-1-thread-1  1713004311185  4  执行任务

The results confirm that, after the initial task, the remaining tasks run from highest to lowest priority.

In conclusion, the article recommends that beginners master LinkedBlockingQueue and SynchronousQueue as foundational queue choices, and then explore LinkedBlockingDeque for simple head‑insertion priority and PriorityBlockingQueue for fine‑grained multi‑priority scheduling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyThreadPoolPriorityQueueBlockingQueueLinkedBlockingDeque
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.