Definition
transfer(e)blocks the producer until a consumer callstake()orpoll()and receives the element – direct hand-off with guaranteed deliveryput(e)places the element in the queue and returns immediately, even if no consumer is readytryTransfer(e)returns false immediately if no consumer is waiting; the element is NOT queuedtryTransfer(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.
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();
}
}
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.
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();
}
}
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 toput()for queued delivery.
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();
}
}
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
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.
---