JavaNIO-Channel

前言

上一篇分析了Buffer,这一篇继续来分析jdk nio中另一个比较重要的类-Channel。通道可以打开和关闭。

类图

这里只是选取了一部分

JavaNIO-Channel_第1张图片JavaNIO-Channel_第2张图片

Channel

Channel接口仅仅有两个方法。
是否打开和关闭。


public interface Channel extends Closeable {
    public boolean isOpen();
    public void close() throws IOException;
}

源码中对close方法的描述为:如果channel已经关闭了再次执行close方法是无效的,如果一个线程正在执行close方法,另一个线程同时也执行了close方法,则会阻塞直至第一个线程从该方法返回。实际就是简单的上锁。

InterruptibleChannel

描述:A channel that can be asynchronously closed and interrupted.
可以异步关闭和中断的channel。

关于异步关闭:我的理解是当有线程由于IO操作在channel的方法挂起的时候,另外的线程可以直接关闭这个channel,但是这就需要channel主动通知挂起的线程,让挂起的线程抛出异常返回。

这个接口没有增加任何方法。仅仅起到一个声明的作用。
这个接口对close方法增加了一条描述:
Any thread currently blocked in an I/O operation upon this channel will receive an {@link AsynchronousCloseException}.
任何由于IO中断在这个channel的线程,在另一个线程执行了close方法之后会抛出一个 AsynchronousCloseException并返回。

AbstractInterruptibleChannel

类图:
JavaNIO-Channel_第3张图片
基本属性

    private final Object closeLock = new Object();
    private volatile boolean closed;
    
	// -- Interruption machinery --
    private Interruptible interruptor;
    private volatile Thread interrupted; // 可能需要通知的线程

AbstractInterruptibleChannel.begin
方法描述:Marks the beginning of an I/O operation that might block indefinitely.
为了实现异步中断和异步关闭,这个方法应该在可能要因为IO操作而阻塞之前执行,并且应该通过try finally块与 end()方法同时执行。如果当前线程在将要因为执行IO操作而进入阻塞之前,判断一下线程是否已经被中断了,如果是则及时关闭channel,是十分有必要的。
什么是异步关闭呢?就是随时可以关闭,直接关闭,不管有没有线程正在这个channel上执行IO操作。
贴一下close方法吧
AbstractInterruptibleChannel.close

    public final void close() throws IOException {
        synchronized (closeLock) {
            if (closed)
                return;
            closed = true;
            implCloseChannel();
        }
    }
 protected final void begin() {
 		// 如果可能要通知的线程为null
        if (interruptor == null) {
        	// 方法内部类
            interruptor = new Interruptible() {
            		// 通知target线程关闭channel
                    public void interrupt(Thread target) {
                        synchronized (closeLock) {
                            if (closed)
                                return;
                            closed = true;
                            interrupted = target;
                            try {
                                AbstractInterruptibleChannel.this.implCloseChannel();
                            } catch (IOException x) { }
                        }
                    }};
        }
        blockedOn(interruptor);
        Thread me = Thread.currentThread();
        // 如果当前线程被中断了
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }

这个方法的作用是在线程可能会因为IO操作阻塞之前,判断线程的中断标记,如果已经被中断了,那么就要及时关闭channel,否则可能会有内存泄漏的风险。因为线程的中断标记已经为true了,线程随时可能转为其他非running状态。
至于为什么interrupt(Thread target)这个方法要封装到一个内部类,再将引用赋值给线程的一个成员变量,而不是直接写在外面,判断线程如果已经被中断则立即调用这个方法,我想了很久也没搞清楚,将这个方法放在外面明明也可以起到相同的效果呀?
blockedOn(interruptor)最终是调用了Thread.blockedOn(Interruptible b)

    /* Set the blocker field; invoked via jdk.internal.misc.SharedSecrets
    * from java.nio code
    */
   static void blockedOn(Interruptible b) {
       Thread me = Thread.currentThread();
       synchronized (me.blockerLock) {
           me.blocker = b;
       }
   }

AbstractInterruptibleChannel.end

    protected final void end(boolean completed)
        throws AsynchronousCloseException
    {
        blockedOn(null);
        Thread interrupted = this.interrupted;
        // 看前面的begin方法。
        if (interrupted != null && interrupted == Thread.currentThread()) {
            this.interrupted = null;
            throw new ClosedByInterruptException();
        }
        // IO操作没有完成,但是别的线程已经关闭了channel
        if (!completed && closed)
            throw new AsynchronousCloseException();
    }

begin和end方法光说可能有点抽象,随便找一个实现类,比如SocketChannelImpl,看看含有IO操作的方法就明白这两个方法有什么用了。
AbstractInterruptibleChannel就分析到这。

SelectableChannel

这是一个比较重要的抽象类,这个接口定义了一种可以基于选择器Seletor进行多路复用的Channel。
类图:
JavaNIO-Channel_第4张图片

demo

接下来我要完成一个基于jdk NIO服务端的例子,尽管编程方式非常的反人类,但还是写一下吧。

/**
 * @author delicious
 * @ClassName: MyServer
 * @describe 利用jdk NIO 实现的聊天服务器
 * @createDate 2020/4/19
 */
@Slf4j
public class MyNioServer {


    private final ServerSocketChannel serverChannel = ServerSocketChannel.open();
    private Selector selector = Selector.open();

    public MyNioServer() throws IOException {
        serverChannel.socket().bind(new InetSocketAddress(8888));
        serverChannel.configureBlocking(false);
        // 将通道和感兴趣的IO事件注册到选择器
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }



    public void start() throws IOException {
        for (; ; ) {
            // 阻塞直至有感兴趣的并且可以处理的IO事件
            selector.select();
            log.info("有待处理的io事件!");
            // 获得所有可以处理的、已经注册了的IO事件
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                if (key.isAcceptable()) {
                    SocketChannel channel = serverChannel.accept();
                    log.info("接受一个连接,生成一个channel");
                    channel.configureBlocking(false);
                    // 注册到选择器,这就是多路复用了
                    channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                } else if(!key.channel().isOpen()){
                    log.info("close.");
                    key.channel().close();
                } else if (key.isReadable()) {
                    log.info("readable...");
                    // 通过key反向获取channel
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    try {
                        channel.read(byteBuffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                        channel.close();
                    }
                    String s = new String(byteBuffer.array());
                    log.info("server接收到消息:"+s);
                }
                it.remove();
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        MyNioServer server = new MyNioServer();
        Thread thread = new Thread(() -> {
            try {
                server.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        SocketChannel channel = SocketChannel.open(new InetSocketAddress(8888));
        channel.write(ByteBuffer.wrap("hello world".getBytes()));
        thread.join();
    }
}

输出:
JavaNIO-Channel_第5张图片

AbstractSelectableChannel.register

    public final SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException
    {
    	// 校验ops的值是否合法
        if ((ops & ~validOps()) != 0)
            throw new IllegalArgumentException();
        // 检查channel现在是否以及关闭了
        if (!isOpen())
            throw new ClosedChannelException();
        // 同时只能由一个线程执行此方法
        synchronized (regLock) {
            if (isBlocking())
                throw new IllegalBlockingModeException();
            // 该线程需要同时拿到keyLock才嫩继续操作
            synchronized (keyLock) {
                // re-check if channel has been closed
                if (!isOpen())
                    throw new ClosedChannelException();
                SelectionKey k = findKey(sel);
                if (k != null) {
                	// 将传入的对象attach到key,这样之后可以从key取出
                    k.attach(att);
                    k.interestOps(ops);
                } else {
                    // New registration
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
                return k;
            }
        }
    }

我们主要到调用了findKey方法,这个方法遍历该channel所有的keys,如果发现有key对应的选择器是将要注册到的选择器,就直接返回这个key,然后修改一下i感兴趣的事件就可以继续用了,否则就要new一个SelectionKey。
AbstractSelectableChannel.findKey

    private SelectionKey findKey(Selector sel) {
    	// 此时不可能有别的线程修改keys
        assert Thread.holdsLock(keyLock);
        if (keys == null)
            return null;
        for (int i = 0; i < keys.length; i++)
            if ((keys[i] != null) && (keys[i].selector() == sel))
                return keys[i];
        return null;
    }

SelectionKey.attach

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

SelectionKey.attachmentUpdater
字面意思,专门用来负责对attachment进行原子操作的原子操作类。

    private static final AtomicReferenceFieldUpdater<SelectionKey,Object>
        attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SelectionKey.class, Object.class, "attachment"
        );

发现调用了juc原子操作类 AtomicReferenceFieldUpdater的方法
AtomicReferenceFieldUpdater.getAndSet()

    public V getAndSet(T obj, V newValue) {
       V prev;
       do {
           prev = get(obj);
       } while (!compareAndSet(obj, prev, newValue));
       return prev;
   }

简单的一个自旋CAS,将obj的字段原子性操作更换为新的值。
至于为什么要使用CAS保证更新的原子性,因为attach方法返回的是
previously-attached object, if any,意思就是attach方法返回的对象和被替换的对象必须一致,而get 和 set需要使用原子操作类的.getAndSet方法通过CAS保证操作的原子性。

你可能感兴趣的:(java,NIO,java)