SelectionKey
7 mins
The SelectionKey class represents a channel’s registration with a selector, maintaining interest and ready operation sets along with attachment capabilities.
Source Code #
Core Implementation #
public abstract class SelectionKey {
protected SelectionKey() { }
public abstract SelectableChannel channel();
public abstract Selector selector();
public abstract boolean isValid();
public abstract void cancel();
public abstract int interestOps();
public abstract SelectionKey interestOps(int ops);
public int interestOpsOr(int ops) {
synchronized (this) {
int oldVal = interestOps();
interestOps(oldVal | ops);
return oldVal;
}
}
public int interestOpsAnd(int ops) {
synchronized (this) {
int oldVal = interestOps();
interestOps(oldVal & ops);
return oldVal;
}
}
public abstract int readyOps();
// Operation bits
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
// Convenience methods
public final boolean isReadable() {
return (readyOps() & OP_READ) != 0;
}
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
public final boolean isConnectable() {
return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isAcceptable() {
return (readyOps() & OP_ACCEPT) != 0;
}
// Attachments
private static final VarHandle ATTACHMENT = MhUtil.findVarHandle(
MethodHandles.lookup(), "attachment", Object.class);
private volatile Object attachment;
public final Object attach(Object ob) {
return ATTACHMENT.getAndSet(this, ob);
}
public final Object attachment() {
return attachment;
}
}
Implementation Details #
SelectionKeyImpl Concrete Class #
The platform-specific implementation extends AbstractSelectionKey:
public final class SelectionKeyImpl extends AbstractSelectionKey {
private static final VarHandle INTERESTOPS = ConstantBootstraps.fieldVarHandle(
MethodHandles.lookup(), "interestOps", VarHandle.class,
SelectionKeyImpl.class, int.class);
private final SelChImpl channel;
private final SelectorImpl selector;
private volatile int interestOps;
private volatile int readyOps;
// Platform-specific fields
private int registeredEvents; // Events registered in kernel
private volatile boolean reset; // Needs re-registration
private int index; // Index in pollfd array
private int lastPolled; // Last poll timestamp
SelectionKeyImpl(SelChImpl ch, SelectorImpl sel) {
channel = ch;
selector = sel;
}
@Override
public SelectableChannel channel() {
return (SelectableChannel)channel;
}
@Override
public Selector selector() {
return selector;
}
@Override
public int interestOps() {
ensureValid();
return interestOps;
}
@Override
public SelectionKey interestOps(int ops) {
ensureValid();
if ((ops & ~channel().validOps()) != 0)
throw new IllegalArgumentException();
int oldOps = (int) INTERESTOPS.getAndSet(this, ops);
if (ops != oldOps) {
selector.setEventOps(this); // Notify selector of change
}
return this;
}
@Override
public int readyOps() {
ensureValid();
return readyOps;
}
// Internal NIO methods (not affected by validity checks)
public void nioReadyOps(int ops) {
readyOps = ops;
}
public int nioReadyOps() {
return readyOps;
}
public SelectionKey nioInterestOps(int ops) {
if ((ops & ~channel().validOps()) != 0)
throw new IllegalArgumentException();
interestOps = ops;
selector.setEventOps(this);
return this;
}
public int nioInterestOps() {
return interestOps;
}
int translateInterestOps() {
return channel.translateInterestOps(interestOps);
}
boolean translateAndSetReadyOps(int ops) {
return channel.translateAndSetReadyOps(ops, this);
}
boolean translateAndUpdateReadyOps(int ops) {
return channel.translateAndUpdateReadyOps(ops, this);
}
void registeredEvents(int events) {
this.registeredEvents = events;
}
int registeredEvents() {
return registeredEvents;
}
void reset() {
reset = true;
selector.setEventOps(this);
selector.wakeup();
}
boolean getAndClearReset() {
boolean r = reset;
if (r) reset = false;
return r;
}
}
Operation Set Bits #
| Operation | Bit Value | Channel Support | Description |
|---|---|---|---|
OP_READ | 1 (1«0) | SocketChannel, DatagramChannel, Pipe.SourceChannel | Channel ready for reading |
OP_WRITE | 4 (1«2) | SocketChannel, DatagramChannel, Pipe.SinkChannel | Channel ready for writing |
OP_CONNECT | 8 (1«3) | SocketChannel | Connection established or failed |
OP_ACCEPT | 16 (1«4) | ServerSocketChannel | Incoming connection available |
Attachment Mechanism #
Selection keys support attaching arbitrary application data using a VarHandle-based atomic attachment mechanism:
private static final VarHandle ATTACHMENT = MhUtil.findVarHandle(
MethodHandles.lookup(), "attachment", Object.class);
private volatile Object attachment;
public final Object attach(Object ob) {
return ATTACHMENT.getAndSet(this, ob);
}
public final Object attachment() {
return attachment;
}
Characteristics:
- Atomic updates:
getAndSetensures thread-safe attachment changes - Single attachment: Only one object can be attached at a time
- Null allowed:
nullcan be attached to clear previous attachment - Volatile visibility: Changes are immediately visible to other threads
Validity and Cancellation #
A selection key remains valid until:
- Explicit cancellation via
cancel()method - Channel closure
- Selector closure
Cancellation adds the key to the selector’s cancelled-key set for removal during the next selection operation. The isValid() method returns false once cancelled.
private void ensureValid() {
if (!isValid())
throw new CancelledKeyException();
}
Thread Safety #
- SelectionKey operations: Thread-safe for concurrent access
- Interest set changes: Atomic via
VarHandleoperations - Ready set updates: Only modified by selector during selection operations
- Attachment: Atomic
getAndSetensures consistency
Platform-Specific Fields #
Different selector implementations use platform-specific fields:
| Field | Purpose | Used By |
|---|---|---|
registeredEvents | Events registered in kernel (epoll/poll) | EPollSelectorImpl, PollSelectorImpl |
index | Index in native pollfd array | PollSelectorImpl |
lastPolled | Timestamp of last selection | Monitoring/debugging |
reset | Flag indicating need for re-registration | All selectors |
Common Usage Patterns #
Basic Key Processing #
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
// Process ready events
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
key.cancel();
channel.close();
}
}
// Update interest ops
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// Attach application data
ConnectionState state = new ConnectionState();
key.attach(state);
// Later retrieve attachment
ConnectionState attached = (ConnectionState) key.attachment();
State Management with Attachments #
class ConnectionHandler {
private SelectionKey key;
public void handleRead() {
ByteBuffer buffer = ByteBuffer.allocate(4096);
SocketChannel channel = (SocketChannel) key.channel();
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
key.cancel();
channel.close();
return;
}
buffer.flip();
processData(buffer);
// Switch to write mode when response ready
key.interestOps(SelectionKey.OP_WRITE);
}
public void handleWrite() {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer response = getResponse();
channel.write(response);
if (!response.hasRemaining()) {
// Switch back to read mode
key.interestOps(SelectionKey.OP_READ);
}
}
}
// Register with attachment
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
key.attach(new ConnectionHandler(key));
Interest Ops Management #
// Safe interest ops updates
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
// Using atomic operations (Java 11+)
int previousOps = key.interestOpsOr(SelectionKey.OP_WRITE);
int clearedOps = key.interestOpsAnd(~SelectionKey.OP_READ);
// Batch interest ops changes
int newOps = SelectionKey.OP_READ;
if (shouldWrite) newOps |= SelectionKey.OP_WRITE;
if (shouldAccept) newOps |= SelectionKey.OP_ACCEPT;
key.interestOps(newOps);
Key Validation Patterns #
// Always check validity before operations
if (key.isValid()) {
if (key.isReadable()) {
// Process read
}
} else {
// Clean up resources
cleanup(key);
}
// Ensure validity in critical sections
try {
key.interestOps(SelectionKey.OP_WRITE);
} catch (CancelledKeyException e) {
// Key was cancelled concurrently
handleCancelledKey(key);
}
Best Practices #
- Check Key Validity: Always call
isValid()before key operations - Use Atomic Interest Ops: Prefer
interestOpsOr()andinterestOpsAnd()for thread-safe updates - Attach Lightweight Objects: Keep attachments small and simple
- Clear Attachments: Set attachment to
nullwhen no longer needed - Handle Cancellation: Always be prepared for
CancelledKeyException - Avoid Interest Ops Accumulation: Clear bits before setting new interest ops
- Use Ready Ops Hints: Ready ops are hints, not guarantees
- Close Channels Properly: Channel closure automatically cancels keys
- Monitor Key Count: Large numbers of keys impact selector performance
- Avoid Blocking in Key Handlers: Keep key processing fast to prevent selector starvation
Common Pitfalls #
- Ignoring CancelledKeyException: Not handling concurrent key cancellation
- Assuming Ready Ops Accuracy: Ready ops may become stale immediately after selection
- Attachment Memory Leaks: Forgetting to clear attachments when keys are cancelled
- Interest Ops Race Conditions: Concurrent interest ops updates without atomic operations
- Blocking in Key Processing: Causing selector to stall for other channels
- Not Removing Invalid Keys: Keeping references to cancelled keys
- Incorrect Interest Ops Combinations: Setting unsupported ops for channel type
- Forgetting to Cancel Keys: Leading to selector resource leaks
- Attachment Type Safety: Not casting attachments safely
- Ignoring Platform Limits: Exceeding platform-specific key limits
Performance Considerations #
Interest Ops Updates #
- Atomic operations:
interestOpsOr()andinterestOpsAnd()useVarHandlefor atomic updates - Selector notification: Interest ops changes trigger selector wakeup
- Batch updates: Minimize interest ops changes to reduce selector overhead
Attachment Overhead #
- Memory footprint: Each attachment adds to heap usage
- GC pressure: Large attachments increase garbage collection
- Thread-local alternatives: Consider thread-local storage for heavy state
Key Lifecycle Management #
- Creation overhead: Key creation involves allocation and registration
- Cancellation cost: Cancelled keys are processed during selection operations
- Reuse patterns: Reuse keys where possible (though not directly supported)
Related Classes #
Selector #
- Multiplexes multiple selectable channels
- Creates and manages selection keys
- Selector Details
SelectableChannel #
- Channel that can be registered with a selector
- Defines valid operations for selection keys
- SelectableChannel Details
SocketChannel #
- TCP socket channel supporting
OP_READ,OP_WRITE,OP_CONNECT - Most common channel type used with selection keys
- SocketChannel Details
ServerSocketChannel #
- TCP server socket channel supporting
OP_ACCEPT - Used for accepting incoming connections
- ServerSocketChannel Details
DatagramChannel #
- UDP datagram channel supporting
OP_READ,OP_WRITE - Connectionless channel with selection support
- DatagramChannel Details
Pipe #
- Intra-JVM communication channels
Pipe.SourceChannelsupportsOP_READPipe.SinkChannelsupportsOP_WRITE- Pipe Details