Back to Queue, Deque & Stack
Q7Question 7 of 8

What is BlockingDeque?

Key Concepts
  • BlockingDeque combines BlockingQueue (blocking put/take) with Deque (double-ended access), providing blocking operations at both ends
  • LinkedBlockingDeque is the standard implementation: optionally bounded, backed by doubly-linked nodes
  • putFirst()/putLast() block if the deque is full; takeFirst()/takeLast() block if empty
  • Work-stealing pattern: workers take from their own deque's head; idle workers steal from another's tail, minimizing contention
  • ForkJoinPool uses work-stealing deques internally, and parallel streams use ForkJoinPool
  • Use bounded capacity to prevent OOM; unbounded (default) allows unlimited growth

Definition

BlockingDeque is a double-ended blocking queue where both ends support blocking put and take, enabling work-stealing patterns where workers take from their own head and steal from others' tails.
  • BlockingDeque extends both BlockingQueue and Deque, providing blocking operations at both ends
  • LinkedBlockingDeque is the standard implementation: optionally bounded, backed by doubly-linked nodes with a single ReentrantLock
  • Blocking methods: putFirst(e)/putLast(e) block if full; takeFirst()/takeLast() block if empty
  • Work-stealing: each worker thread has its own deque, takes tasks from its head, and steals from another worker's tail when idle
  • ForkJoinPool uses this pattern internally for parallel stream execution

Each bank teller has their own stack of transaction forms (a deque). Normally, a teller takes forms from the top of their stack (takeFirst). When a teller finishes their stack, they steal forms from the bottom of a busy teller's stack (takeLast). Since one teller works from the top and the thief works from the bottom, they rarely bump into each other. This is work-stealing – it keeps all tellers busy without wasting time waiting.

Understanding the Concepts

What is BlockingDeque?

BlockingDeque combines the features of BlockingQueue (blocking put/take) with Deque (operations at both ends). This gives you four blocking operations:

  • putFirst(e) – insert at head, block if full.
  • putLast(e) – insert at tail, block if full.
  • takeFirst() – remove from head, block if empty.
  • takeLast() – remove from tail, block if empty.
JAVA
package com.bank.service;

import java.util.concurrent.*;

public class BlockingDequeDemo {
    public static void main(String[] args) throws InterruptedException {
        // Bounded blocking deque: max 100 items
        BlockingDeque<String> deque = new LinkedBlockingDeque<>(100);

        // Producer adds to tail
        deque.putLast("TXN-001: Deposit 50000");
        deque.putLast("TXN-002: Withdraw 10000");
        deque.putLast("TXN-003: Transfer 25000");

        // Consumer takes from head (normal processing)
        String txn = deque.takeFirst();
        System.out.println("Normal processing: " + txn);

        // Work-stealing: thief takes from tail
        String stolen = deque.takeLast();
        System.out.println("Stolen task: " + stolen);

        // Non-blocking variants
        String polled = deque.pollFirst(1, TimeUnit.SECONDS);
        System.out.println("Timed poll: " + polled);
    }
}
TEXT
Normal processing: TXN-001: Deposit 50000
Stolen task: TXN-003: Transfer 25000
Timed poll: TXN-002: Withdraw 10000

How does work-stealing work?

In a work-stealing architecture, each worker thread has its own deque of tasks. The worker takes tasks from the head of its own deque. When a worker's deque is empty, it steals tasks from the tail of another worker's deque.

The key insight: since the worker and the thief operate at opposite ends of the deque, contention is minimal. The worker processes from the head (most recently added tasks, which are "hottest" in cache). The thief steals from the tail (oldest tasks), which does not interfere with the worker's processing.

This pattern maximizes CPU utilization because idle workers do not sit idle – they help busy workers by stealing their excess tasks.

JAVA
package com.bank.demo;

import java.util.concurrent.*;
import java.util.*;

public class WorkStealingConceptDemo {
    public static void main(String[] args) throws InterruptedException {
        // Simulate 3 workers with work-stealing deques
        LinkedBlockingDeque<String> worker1Queue = new LinkedBlockingDeque<>();
        LinkedBlockingDeque<String> worker2Queue = new LinkedBlockingDeque<>();
        LinkedBlockingDeque<String> worker3Queue = new LinkedBlockingDeque<>();

        // Worker 1 has lots of tasks
        for (int i = 1; i <= 10; i++) {
            worker1Queue.putLast("Task-" + i);
        }
        // Workers 2 and 3 are idle

        // Worker 1 processes from its head
        System.out.println("Worker 1 takes: " + worker1Queue.pollFirst());

        // Worker 2 steals from Worker 1's tail (opposite end!)
        String stolen = worker1Queue.pollLast();
        System.out.println("Worker 2 steals: " + stolen);

        // Worker 3 also steals
        stolen = worker1Queue.pollLast();
        System.out.println("Worker 3 steals: " + stolen);

        System.out.println("Remaining in Worker 1: " + worker1Queue.size());
    }
}
TEXT
Worker 1 takes: Task-1
Worker 2 steals: Task-10
Worker 3 steals: Task-9
Remaining in Worker 1: 7

How does ForkJoinPool use work-stealing?

ForkJoinPool (introduced in Java 7) is the backbone of parallel streams. It implements work-stealing internally. Each worker thread in the pool has its own deque. When a task is forked (split into subtasks), the subtasks go into the current thread's deque. Other threads steal subtasks when idle.

JAVA
package com.bank.demo;

import java.util.*;
import java.util.concurrent.*;
import java.math.BigDecimal;

public class ForkJoinWorkStealingDemo {
    public static void main(String[] args) {
        // Parallel stream uses ForkJoinPool internally
        List<BigDecimal> transactions = new ArrayList<>();
        for (int i = 0; i < 1_000_000; i++) {
            transactions.add(BigDecimal.valueOf(i));
        }

        // ForkJoinPool splits work across threads using work-stealing deques
        BigDecimal total = transactions.parallelStream()
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        System.out.println("Total: " + total);
        System.out.println("Pool parallelism: " + ForkJoinPool.commonPool().getParallelism());
    }
}
TEXT
Total: 499999500000
Pool parallelism: 7

What should a senior engineer know?

- LinkedBlockingDeque uses a single lock. Unlike LinkedBlockingQueue (two locks), LinkedBlockingDeque uses one ReentrantLock because both ends can be accessed concurrently by workers and thieves, and coordinating two locks for a deque is more complex.

- ForkJoinPool's deque is not LinkedBlockingDeque. ForkJoinPool uses a specialized internal deque implementation optimized for work-stealing with CAS operations, not the general-purpose LinkedBlockingDeque.

- Bounded vs unbounded. Always use bounded: new LinkedBlockingDeque<>(capacity). Unbounded risks OOM if tasks accumulate faster than workers process them.

What are common interview follow-up questions?

  • "What is work-stealing?" – Idle workers steal tasks from busy workers' deques.
  • "Where is work-stealing used in Java?" – ForkJoinPool, parallel streams.
  • "Why do worker and thief operate at opposite ends?" – Minimizes contention.
  • "What is the difference between BlockingQueue and BlockingDeque?" – BlockingDeque allows blocking at both ends.

Visual Overview

DIAGRAM
  Work-stealing pattern

  Worker 1 (busy)         Worker 2 (idle)
  ┌──────────────────┐
  │ HEAD  → Task-1   │ ← Worker 1 takes from HEAD
  │         Task-2   │
  │         Task-3   │
  │         Task-4   │
  │         Task-5   │
  │ TAIL  → Task-6   │ ← Worker 2 STEALS from TAIL
  └──────────────────┘

  Minimal contention: opposite ends!

  ForkJoinPool:
  ┌────────────────────────────────────┐
  │  Thread-0 deque: [A1, A2, A3]     │
  │  Thread-1 deque: [B1, B2]         │
  │  Thread-2 deque: [] ← empty       │
  │                                    │
  │  Thread-2 idle → steals A3 from   │
  │  Thread-0's tail                   │
  └────────────────────────────────────┘

Key Takeaway

BlockingDeque extends both BlockingQueue and Deque, providing blocking operations at both ends. LinkedBlockingDeque is the standard implementation, supporting bounded capacity and blocking putFirst/putLast/takeFirst/takeLast.

The primary use case is the work-stealing pattern. Each worker thread has its own deque. It takes tasks from the head (its own work) and, when idle, steals from the tail of another worker's deque. Since worker and thief operate at opposite ends, contention is minimal. This is how ForkJoinPool and parallel streams achieve high CPU utilization.

In the National Bank of India's parallel trade processing system, each processor thread has its own task deque. When the HDFC equity processor finishes its tasks, it steals pending trades from the ICICI bond processor's deque. This keeps all processors busy without centralized task distribution.

Remember one sentence: BlockingDeque enables work-stealing where workers take from their own head and thieves steal from others' tails, keeping all threads busy with minimal contention.

---

Share
Use ← → arrow keys to navigate