Taking FileChannel for example in the previous post may bewilder audiences: it’s non-blocking, so how does NIO achievement non-blocking? Here comes Selector.

A Selector is a Java NIO component which can examine one or more NIO Channel’s, and determine which channels are ready for reading or writing. A thread can manage multiply channels with Selector making multiple network connections feasible. This way avoids the cost of switching between threads which is expensive.

Here is an illustration of a thread using a Selector to handle 3 Channels:

Creating a Selector

You create a Selector by calling the Selector.open() method, like this: Selector selector = Selector.open();.

Loot at the multilevel inheritance hierarchy.

Selector, even an abstract class, implements open() method to open a selector. The new selector is created by invoking SelectorProvider#openSelector method.

// Selector.java

public abstract class Selector implements Closeable {
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }
}

Regarding to SelectorProvider#openSelector, is also abstract, but instantiates a system-default provider class.

// SelectorProvider.java

public abstract class SelectorProvider {
    private static SelectorProvider provider = null;

    public abstract AbstractSelector openSelector()
        throws IOException;

    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }
}

For Linux, it’s EpollSelectorProvider.

// DefaultSelectorProvider.java

public class DefaultSelectorProvider {

    /**
     * Prevent instantiation.
     */
    private DefaultSelectorProvider() { }

    /**
     * Returns the default SelectorProvider.
     */
    public static SelectorProvider create() {
        return new EPollSelectorProvider();
    }
}

Registering Channels with the Selector

Before you use a Channel with a Selector, you must register the Channel with the Selector. Calling SelectableChannel.register() method can attain that.

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Only SelectableChannel can register with Selector, and the children of SelectableChannel must implement configureBlocking, which excludes FileChannel.

AbstractSelectableChannel, the child of SelectableChannel implements register() method by the following steps:

  • get the lock
  • check if this channel is open
  • check whether or not the events you want to listen for is valid
  • check if this channel is in blocking mode
  • check if thew keys have been created
  • call AbstractSelector#register method
// AbstractSelectableChannel.java

public final SelectionKey register(Selector sel, int ops,
                                   Object att)
    throws ClosedChannelException
{
    synchronized (regLock) {
        if (!isOpen())
            throw new ClosedChannelException();
        if ((ops & ~validOps()) != 0)
            throw new IllegalArgumentException();
        if (blocking)
            throw new IllegalBlockingModeException();
        SelectionKey k = findKey(sel);
        if (k != null) {
            k.interestOps(ops);
            k.attach(att);
        }
        if (k == null) {
            // New registration
            synchronized (keyLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                k = ((AbstractSelector)sel).register(this, ops, att);
                addKey(k);
            }
        }
        return k;
    }
}

As to AbstractSelector#register, SelectorImpl derived from AbstractSelector implements the register method but leaves central work to an abstract method impleRegister.

// SelectorImpl.java

protected final SelectionKey register(AbstractSelectableChannel ch,
                                      int ops,
                                      Object attachment)
{
    if (!(ch instanceof SelChImpl))
        throw new IllegalSelectorException();
    SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
    k.attach(attachment);
    synchronized (publicKeys) {
        implRegister(k);
    }
    k.interestOps(ops);
    return k;
}

protected abstract void implRegister(SelectionKeyImpl ski);

EPollSelectorImpl, the offspring of SelectorImpl fills impleRegister. Few member variables involved include:

  • closed: True if this Selector has been closed
  • fdToKey: Maps from file descriptors to keys
  • pollWrapper: The poll object
// EPollSelectorImpl.java

class EPollSelectorImpl
    extends SelectorImpl
{
    private volatile boolean closed;
    private Map<Integer,SelectionKeyImpl> fdToKey;
    EPollArrayWrapper pollWrapper;


    protected void implRegister(SelectionKeyImpl ski) {
        if (closed)
            throw new ClosedSelectorException();
        SelChImpl ch = ski.channel;
        int fd = Integer.valueOf(ch.getFDVal());
        fdToKey.put(fd, ski);
        pollWrapper.add(fd);
        keys.add(ski);
    }
}

Notice the second parameter of the register() method. This is an “interest set”, meaning what events you are interested in listening for in the Channel, via the Selector. There are four different events you can listen for and represented by the four SelectionKey constants:

EventSelecttionKey constant
ConnectSelectionKey.OP_CONNECT
AcceptSelectionKey.OP_ACCEPT
ReadSelectionKey.OP_READ
WriteSelectionKey.OP_WRITE

SelectionKey

When you register a channel with a Selector the Channel.register() method returns a SelectionKeyobject. This key represents that channels registration with that selector.

This SelectionKey object contains a few interesting properties:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

Interest Set

The interest set determines which operation categories will be tested for readiness the next time one of the selector’s selection methods is invoked. The interest set is initialized with the value given when the key is created; it may later be changed via the interestOps(int) method or retrieves via the interestOps().

// SelectionKey.java

public abstract class SelectionKey {
  public abstract int interestOps();
  public abstract SelectionKey interestOps(int ops);
}

you can AND the interest set with the given SelectionKey constant to find out if a certain event is in the interest set.

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;  

The implementation of interestOps lies in SelectionKeyImpl.

// SelectionKeyImpl.java

public class SelectionKeyImpl
    extends AbstractSelectionKey
{
	final SelChImpl channel; 
    private volatile int interestOps;
    
    private void ensureValid() {
        if (!isValid())
            throw new CancelledKeyException();
    }
    
    public int interestOps() {
        ensureValid();
        return interestOps;
    }

    public SelectionKey interestOps(int ops) {
        ensureValid();
        return nioInterestOps(ops);
    }
    
    public SelectionKey nioInterestOps(int ops) {
        if ((ops & ~channel().validOps()) != 0)
            throw new IllegalArgumentException();
        channel.translateAndSetInterestOps(ops, this);
        interestOps = ops;
        return this;
    }
}

The child of Channel, SocketChannelImpl here, translates native poll recent ops into a ready operation ops with SocketChannelImpl#translateAndSetInterestOps method.

Ready Set

The ready set identifies the operation categories for which the key’s channel has been detected to be ready by the key’s selector. The ready set is initialized to zero when the key is created; it may later be updated by the selector during a selection operation, but it cannot be updated directly.

The readyOps() method in SelectionKey opens the door to retrieve this key’s ready-operation set.

// SelectionKey.java

public abstract class SelectionKey {
    public abstract int readyOps();
}

You can test in the same way as with the interest set, what events / operations the channel is ready for. But, you can also use these four methods instead, which all reaturn a boolean:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel + Selector

Accessing the channel + selector from the SelectionKey is trivial. Here is how it’s done:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();  

Remember the structure of SelectionKeyImpl? It has a package-private member named channel and a public one selector.

// SelectionKeyImpl.java

public class SelectionKeyImpl
    extends AbstractSelectionKey
{
	final SelChImpl channel;                            // package-private
	public final SelectorImpl selector;
	
	public SelectableChannel channel() {
        return (SelectableChannel)channel;
    }
    
    public Selector selector() {
        return selector;
    }
}

Attaching Objects

You can attach an object to a SelectionKey providing a handy way of recognizing a given channel.

Here is how you attach objects:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

AtomicReferenceFieldUpdater, a reflection-based utility, is engaged in to that enable atomic updates to designated volatile reference fields of designated classes.

// SelectionKey.java
    
public abstract class SelectionKey {    
    private volatile Object attachment = null;
    private static final AtomicReferenceFieldUpdater<SelectionKey,Object>
        attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SelectionKey.class, Object.class, "attachment"
        );

    public final Object attach(Object ob) {
        return attachmentUpdater.getAndSet(this, ob);
    }

    public final Object attachment() {
        return attachment;
    }
}

Selecting Channels via a Selector

Once you have registered one or more channels with a Selector, you can call one of the select() methods. These methods select a set of keys whose corresponding channels are ready for I/O operations(connect, accept, read or write).

Here are the select() methods:

  • int select() performs a blocking selection operation. It returns only after at least one channel is selected, this selector’s wakeup method is invoked, or the current thread is interrupted, whichever comes first.
  • int select(long timeout) does the same as select() except it blocks for a maximum of timeout milliseconds.
  • int selectNow() performs a non-blocking selection operation. If no channels have become selectable since the previous selection operation then this method immediately returns zero. Invoking this method clears the effect of any previous invocations of the wakeup method.
// SelectorImpl.java

public abstract class SelectorImpl
    extends AbstractSelector
{
    protected abstract int doSelect(long timeout) throws IOException;

    public int select(long timeout)
        throws IOException
    {
        if (timeout < 0)
            throw new IllegalArgumentException("Negative timeout");
        return lockAndDoSelect((timeout == 0) ? -1 : timeout);
    }

    public int select() throws IOException {
        return select(0);
    }

    public int selectNow() throws IOException {
        return lockAndDoSelect(0);
    }

    private int lockAndDoSelect(long timeout) throws IOException {
        synchronized (this) {
            if (!isOpen())
                throw new ClosedSelectorException();
            synchronized (publicKeys) {
                synchronized (publicSelectedKeys) {
                    return doSelect(timeout);
                }
            }
        }
    }
}

SelectorImpl leaves doSelect(long) unimplemented to its derived class. Let’s see what EPollSelectorImpl does.

// EPollSelectorImpl.java

class EPollSelectorImpl
    extends SelectorImpl
{
    protected int doSelect(long timeout) throws IOException {
        if (closed)
            throw new ClosedSelectorException();
        processDeregisterQueue();
        try {
            begin();
            pollWrapper.poll(timeout);
        } finally {
            end();
        }
        processDeregisterQueue();
        int numKeysUpdated = updateSelectedKeys();
        if (pollWrapper.interrupted()) {
            // Clear the wakeup pipe
            pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
            synchronized (interruptLock) {
                pollWrapper.clearInterrupted();
                IOUtil.drain(fd0);
                interruptTriggered = false;
            }
        }
        return numKeysUpdated;
    }
}

EPollSelectorImpl appeals to EPollArrayWrapper who manipulates a native array of epoll_event structs on Linux. The int returned by the select() methods tells how many channels are ready.

selectedKeys()

Once you have called one of the select() methods and its return value has indicated that one or more channels are ready, you can access the ready channels via the “selected key set”, by calling the selectors selectedKeys() method. Here is how that looks:

Set<SelectionKey> selectedKeys = selector.selectedKeys();  

The implementation of selectedKeys() is in SelectorImpl using a Set named publicSelectedKeys to store keys that are going to be selected.

// SelectorImpl.java

public abstract class SelectorImpl
    extends AbstractSelector
{
    private Set<SelectionKey> publicKeys;             // Immutable
    private Set<SelectionKey> publicSelectedKeys;     // Removal allowed, but not addition

    public Set<SelectionKey> selectedKeys() {
        if (!isOpen() && !Util.atBugLevel("1.4"))
            throw new ClosedSelectorException();
        return publicSelectedKeys;
    }
}

When you register a channel with a Selector the Channel.register() method returns a SelectionKey object. This key represents that channels registration with that selector. It is these keys you can access via the selectedKeySet() method from the SelectionKey.

You can iterate this selected key set to access the ready channels. Here is how that looks:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

Notice the keyIterator.remove() call at the end of each iteration. The Selector does not remove the SelectionKey instances from the selected key set itself. You have to do this, when you are done processing the channel. The next time the channel becomes “ready” the Selector will add it to the selected key set again.

Reference