Back to Queue, Deque & Stack
Q8Question 8 of 8

What is TransferQueue?

Key Concepts
  • transfer(e) blocks the producer until a consumer actually receives the element, guaranteeing direct hand-off
  • put(e) places the element in the queue and returns immediately, even if no consumer is waiting
  • LinkedTransferQueue is unbounded and lock-free (CAS-based), providing high throughput for hand-off scenarios
  • tryTransfer(e) returns false immediately if no consumer is waiting; tryTransfer(e, timeout, unit) waits up to the timeout
  • hasWaitingConsumer() and getWaitingConsumerCount() let producers check if consumers are ready
  • Use case: request-response patterns where the producer needs confirmation that the consumer received the task

Definition

TransferQueue's transfer(e) blocks the producer until a consumer actually takes the element, providing guaranteed hand-off unlike put(e) which is fire-and-forget.
  • transfer(e) blocks the producer until a consumer calls take() or poll() and receives the element – direct hand-off with guaranteed delivery
  • put(e) places the element in the queue and returns immediately, even if no consumer is ready
  • tryTransfer(e) returns false immediately if no consumer is waiting; the element is NOT queued
  • tryTransfer(e, timeout, unit) waits up to the timeout for a consumer
  • LinkedTransferQueue is the standard implementation: unbounded, lock-free (CAS-based), excellent for high-throughput hand-off

put(e) is like dropping a transaction form into the inbox and walking away – whether or not someone picks it up. transfer(e) is like handing the form directly to a person – you wait with your hand extended until someone takes it. If nobody is there, you stand waiting. This guarantees the form was received, not just placed in a pile.

Understanding the Concepts

What is the difference between transfer() and put()?

put(e) and transfer(e) both insert an element into the queue, but they differ in when the producer is unblocked:

  • put(e): the producer is unblocked as soon as the element is placed in the queue. The element may sit in the queue for a long time before a consumer reads it.
  • transfer(e): the producer is unblocked only when a consumer actually takes the element. The producer knows that its element has been received.

This makes transfer() a rendezvous point – a direct hand-off between producer and consumer.

JAVA
package com.bank.service;

import java.util.concurrent.*;

public class TransferQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        TransferQueue<String> queue = new LinkedTransferQueue<>();

        // Consumer thread: takes elements
        Thread consumer = new Thread(() -> {
            try {
                Thread.sleep(500); // simulate delayed consumer
                String txn = queue.take();
                System.out.println("Consumer received: " + txn);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        consumer.start();

        // Producer: transfer blocks until consumer takes
        System.out.println("Producer: calling transfer (will block)...");
        long start = System.nanoTime();
        queue.transfer("TXN-001: High-value trade Rs.10Cr");
        long waited = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Producer: transfer completed after " + waited + " ms");
        System.out.println("Producer: guaranteed consumer received the trade");

        consumer.join();
    }
}
TEXT
Producer: calling transfer (will block)...
Consumer received: TXN-001: High-value trade Rs.10Cr
Producer: transfer completed after 502 ms
Producer: guaranteed consumer received the trade

How does tryTransfer work?

tryTransfer(e) attempts an immediate hand-off. If a consumer is already waiting (blocked in take()), the element is delivered and the method returns true. If no consumer is waiting, the method returns false immediately – the element is NOT queued.

tryTransfer(e, timeout, unit) waits up to the specified timeout for a consumer to arrive.

JAVA
package com.bank.demo;

import java.util.concurrent.*;

public class TryTransferDemo {
    public static void main(String[] args) throws InterruptedException {
        TransferQueue<String> queue = new LinkedTransferQueue<>();

        // No consumer waiting
        boolean delivered = queue.tryTransfer("TXN-001");
        System.out.println("Immediate delivery (no consumer): " + delivered);
        System.out.println("Queue size: " + queue.size()); // 0 – NOT queued!

        // Start a consumer, then try transfer
        Thread consumer = new Thread(() -> {
            try {
                String item = queue.take(); // blocks waiting
                System.out.println("Consumer got: " + item);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        consumer.start();
        Thread.sleep(100); // let consumer start waiting

        delivered = queue.tryTransfer("TXN-002");
        System.out.println("Immediate delivery (consumer waiting): " + delivered);

        consumer.join();
    }
}
TEXT
Immediate delivery (no consumer): false
Queue size: 0
Consumer got: TXN-002
Immediate delivery (consumer waiting): true

How is TransferQueue different from SynchronousQueue?

SynchronousQueue has zero capacity – every put blocks until a consumer takes. LinkedTransferQueue has a queue buffer. When you use put(), elements are queued normally. Only transfer() blocks for hand-off.

This makes LinkedTransferQueue more flexible: you can mix fire-and-forget (put) and guaranteed delivery (transfer) on the same queue.

| Feature | SynchronousQueue | LinkedTransferQueue |
|---|---|---|
| Capacity | 0 (no buffer) | Unbounded |
| put(e) | Blocks until take | Queues immediately |
| transfer(e) | N/A | Blocks until take |
| Flexibility | Only hand-off | Mix of queuing and hand-off |

When should you use TransferQueue?

  • Request-response patterns. Producer submits a task and needs confirmation that a worker received it before logging "task dispatched."
  • Guaranteed delivery. In a banking system, the trade submission service uses transfer() to ensure the settlement engine has actually picked up the trade.
  • Adaptive behavior. Use tryTransfer() to attempt immediate delivery; if no consumer is ready, fall back to put() for queued delivery.
JAVA
package com.bank.service;

import java.util.concurrent.*;

public class AdaptiveDeliveryDemo {
    private static final TransferQueue<String> queue = new LinkedTransferQueue<>();

    // Adaptive: try direct hand-off, fall back to queue
    public static void submit(String trade) throws InterruptedException {
        if (queue.tryTransfer(trade)) {
            System.out.println("Direct hand-off: " + trade);
        } else {
            queue.put(trade);
            System.out.println("Queued for later: " + trade);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // No consumer yet – falls back to queue
        submit("TRD-001: HDFC Buy 100 shares");

        // Start consumer
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    String trade = queue.take();
                    System.out.println("Processing: " + trade);
                    if (trade.contains("TRD-003")) break;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        consumer.start();
        Thread.sleep(100);

        // Consumer is waiting – direct hand-off
        submit("TRD-002: ICICI Sell 50 shares");
        Thread.sleep(100);
        submit("TRD-003: SBI Buy 200 shares");

        consumer.join();
    }
}
TEXT
Queued for later: TRD-001: HDFC Buy 100 shares
Processing: TRD-001: HDFC Buy 100 shares
Direct hand-off: TRD-002: ICICI Sell 50 shares
Processing: TRD-002: ICICI Sell 50 shares
Direct hand-off: TRD-003: SBI Buy 200 shares
Processing: TRD-003: SBI Buy 200 shares

What should a senior engineer know?

- Lock-free implementation. LinkedTransferQueue uses CAS (Compare-And-Swap) operations instead of locks, giving it excellent throughput under contention. It is one of the most efficient queue implementations in the JDK.

- Unbounded only. LinkedTransferQueue is always unbounded. There is no bounded version. If you need back-pressure, combine tryTransfer with fallback logic.

- hasWaitingConsumer(). This method returns true if at least one consumer is blocked in take(). It can be used to make intelligent routing decisions.

- Used in Executors. Some custom executor implementations use LinkedTransferQueue for work dispatch because it can adapt between direct hand-off and queuing.

What are common interview follow-up questions?

  • "What is the difference between transfer() and put()?" – transfer blocks until a consumer receives; put queues and returns.
  • "What happens if tryTransfer has no consumer?" – Returns false; element is NOT queued.
  • "How is it different from SynchronousQueue?" – SynchronousQueue has zero capacity; LinkedTransferQueue has a buffer.
  • "Is LinkedTransferQueue bounded?" – No, always unbounded.

Visual Overview

DIAGRAM
  transfer() vs put()

  put(e):
  Producer ──put(e)──→ [Queue: ..., e] → returns immediately
                       Consumer takes e later (maybe never)

  transfer(e):
  Producer ──transfer(e)──→ BLOCKS ──→ Consumer takes e ──→ Producer unblocks
                                       Guaranteed hand-off!

  tryTransfer(e):
  Consumer waiting?  YES → deliver, return true
                     NO  → return false (element NOT queued)

Key Takeaway

TransferQueue adds a critical capability to blocking queues: guaranteed hand-off. While put() is fire-and-forget – the producer drops the element and moves on – transfer() blocks until a consumer actually receives the element. The producer knows for certain that its work has been picked up.

LinkedTransferQueue is the standard implementation. It is lock-free (CAS-based) and unbounded, providing excellent throughput. The tryTransfer() method offers adaptive behavior: attempt immediate hand-off, and if no consumer is ready, fall back to queuing with put().

In the National Bank of India's trade settlement system, the trade submission service uses transfer() for critical high-value trades. When Rajesh submits a Rs. 10 crore trade, the system blocks until the settlement engine confirms receipt. This guarantees that no high-value trade is lost in transit.

Remember one sentence: transfer() blocks the producer until a consumer receives the element, guaranteeing hand-off, while put() is fire-and-forget.

---

Share
Use ← → arrow keys to navigate