NIO 源码浅析

文章目录

  • 一、前言
    • 1. 整体流程
  • 二、代码详解
    • 1. Selector#open();
      • 1.1 Pipe.open()
      • 1.2 this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
    • 2. Selector.register
      • 1. AbstractSelectableChannel#register
      • 2 SelectorImpl#register
      • 3.WindowsSelectorImpl#implRegister
      • 1.3 SelectionKeyImpl#interestOps(int)
    • 3. SelectionKey.select()
    • 4. WindowsSelectorImpl#wakeup

一、前言

自身尚学艺不精,文章写的一塌糊涂,希望海涵,有错误感谢指正。

1. 整体流程

FD : 文件描述符
ops : 兴趣事件

下面的代码是NIO聊天室启动服务的一段代码

    public void startServer() throws IOException {
        // 开启一个 ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置成非阻塞形式
        serverSocketChannel.configureBlocking(false);
        // 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(PORT));
        // 将通道注册到 Selector 上。该Selector会关心 serverSocketChannel 上的 accept事件
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("## 服务器启动成功");
        while (true) {
            // 如果就绪事件数量大于1。即说明有关心事件就绪
            if (selector.select() > 0) {
                // 获取遍历事件进行处理
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.stream().forEach(Lambda.forEach(key -> hanlder(key)));
                selectionKeys.clear();
            }
        }
    }

下面通过四个关键方法来讲解整个NIO流程

  • Selector#open() :创建了一个 AbstractSelector 的实现类(不同平台上不同的实现类,这里以windows平台为例是 WindowsSelectorImpl)。
  • SelectableChannel#register(java.nio.channels.Selector, int) :将 channel注册到Selector上。
  • Selector#select() :阻塞当前线程,直到有注册在Selector上的channel就绪事件发生。
  • Selector#wakeup() : 上述代码并未体现,用户唤醒阻塞的select 线程。

额外的介绍一个类 PollArrayWrapper。PollArrayWrapper 类中有一个 数组 pollArray (轮询数据) 。是通过 unsafe.allocateMemory((long)var1) 分配的内存。这个数组里面保存了所有的注册channel的文件描述符和兴趣事件。


整个流程简单,简化、简而又简来说:

  1. Selector.open() 创建了一个Pipe (Windows 通过 两个SocketChannel 连接形成,两个SocketChannel 分别是 Source端和Sink端,同时 Source端的FD 将会保存在pollWrapper 中)。同时类中包含一个PollArrayWrapper 对象 pollWrapper。这个对象非常重要,可以简单理解为一个数组,用来保存注册的channel的文件描述符fd 和 channel 对应的兴趣事件(Read, Write, Accept, Connect)。
  2. 当有通道调用register将自己注册到 选择器Selector上时 (调用 SelectableChannel#register(java.nio.channels.Selector, int))方法。即是将 channel 的文件描述符FD 和 兴趣事件ops 保存到 pollWrapper 数组中。
  3. 当调用 Selector#select() 方法时。select() 方法 会调用本地方法 poll0(…)。并将 pollWrapper 地址传递过去,poll0方法根据地址获取到 pollWrapper 的内容后,会不断遍历( 阻塞当前线程 )。当有channel对应的fd 有兴趣事件发生时,则会返回。则select()方法的阻塞结束。
  4. Selector#wakeup()则是唤醒阻塞的select()线程。唤醒的原理是通过第一步中建立的Pipe。根据上面说的原理,pollWrapper 中保存的第一个channel其实是Pipe的Source端。当Sink端有数据准备就绪时,Source端的FD就会处于就绪状态。当用户调用wakeup() 方法时,实际上是调用本地方法 setWakeupSocket0(int var1) 向Sink端写入一个字节,这样Source端的FD就会处于就绪状态,这时第三步中的poll0方法中就会发现有就绪事件,则会直接返回。

二、代码详解

上面简述了整个Selector 的工作流程,下面根据上面贴出的NIO聊天室服务端代码进行具体分析。

1. Selector#open();

Selector.open() 的关键代码在 WindowsSelectorImpl 中(不同平台实现不同,这里是windows平台,所以分析WindowsSelectorImpl)。

整个调用流程如下:
Selector#open() -> WindowsSelectorProvider#openSelector() -> SelectorImpl#WindowsSelectorImpl()

具体代码
SelectorProvider.provider() 会根据平台的不同返回不同实现类,这里以window平台为例,返回 WindowsSelectorProvider

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

这里去看 WindowsSelectorImpl 类的构造方法

public class WindowsSelectorProvider extends SelectorProviderImpl {
    public WindowsSelectorProvider() {
    }

    public AbstractSelector openSelector() throws IOException {
        return new WindowsSelectorImpl(this);
    }

WindowsSelectorImpl 构造函数中做了下面几件事:

  1. 通过 Pipe.open() 创建一个管道。(虽然不是在构造函数中写的,姑且算上在这一步完成的)
  2. 获取到 wakeupSourceFdwakeupSinkFd 两个文件操作符,并将唤醒端的 wakeupSourceFd 放入 pollWrapper 中。当Sink端写入数据时,Source端的FD就会处于就绪状态。(这里将 wakeupSourceFdwakeupSinkFd 放入 pollWrapper 中并不影响用户操作,是为了 wakeup 唤醒操作使用。)

WindowsSelectorImpl 构造方法如下,省略了一些不必要的参数。

// 创建一个管道pipe
 private final Pipe wakeupPipe = Pipe.open();
 // source 端的 fd
 private final int wakeupSourceFd;
 // sink 端的 fd
 private final int wakeupSinkFd;
 // 轮询数组的包装类
 private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);

 // 构造函数
 WindowsSelectorImpl(SelectorProvider var1) throws IOException {
        super(var1);
        // 获取source端的FD
        this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
        // 获取到Sink端的channel
        SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
        var2.sc.socket().setTcpNoDelay(true);
        // 获取Sink 端的 FD
        this.wakeupSinkFd = var2.getFDVal();
        // 将source端的 fd 保存到 pollWrapper 中,默认兴趣事件ops 是 POLLIN, 即输入时间
        this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
    }

下面进行进一步的拆解

1.1 Pipe.open()

按照如下流程 Pipe#open() -> SelectorProviderImpl#openPipe -> PipeImpl#PipeImpl
AccessController.doPrivileged 会调用 PipeImpl.Initializer#run 方法,紧接着调用 PipeImpl.Initializer.LoopbackConnector#run 方法。

    PipeImpl(SelectorProvider var1) throws IOException {
        try {
            AccessController.doPrivileged(new PipeImpl.Initializer(var1));
        } catch (PrivilegedActionException var3) {
            throw (IOException)var3.getCause();
        }
    }

PipeImpl.Initializer.LoopbackConnector#run 如下 :

可以看到 windows下的实现是通过创建两个本地 SocketChannel,连接后形成。两个socketChannel分别实现了管道的source与sink端, 并发送了。并且source端由前面提到的WindowsSelectorImpl放到了pollWrapper中( this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);

   			public void run() {
                ServerSocketChannel var1 = null;
                // 创建两个 SocketChannel  一个作为Pipe的Source端,一个作为sink端
                SocketChannel var2 = null;
                SocketChannel var3 = null;

                try {
                    ByteBuffer var4 = ByteBuffer.allocate(16);
                    ByteBuffer var5 = ByteBuffer.allocate(16);
                    InetAddress var6 = InetAddress.getByName("127.0.0.1");

                    assert var6.isLoopbackAddress();

                    InetSocketAddress var7 = null;

                    while(true) {
                        if (var1 == null || !var1.isOpen()) {
                        	// 开启一个 ServerSocketChannel 服务端 
                            var1 = ServerSocketChannel.open();
                            var1.socket().bind(new InetSocketAddress(var6, 0));
                            var7 = new InetSocketAddress(var6, var1.socket().getLocalPort());
                        }

                        var2 = SocketChannel.open(var7);
                        PipeImpl.RANDOM_NUMBER_GENERATOR.nextBytes(var4.array());
						// 进行链接校验
                        do {
                            var2.write(var4);
                        } while(var4.hasRemaining());

                        var4.rewind();
                        var3 = var1.accept();

                        do {
                            var3.read(var5);
                        } while(var5.hasRemaining());

                        var5.rewind();
                        if (var5.equals(var4)) {
                        	// 指定Pipe的 Source 端和 sink 端
                            PipeImpl.this.source = new SourceChannelImpl(Initializer.this.sp, var2);
                            PipeImpl.this.sink = new SinkChannelImpl(Initializer.this.sp, var3);
                            break;
                        }

                        var3.close();
                        var2.close();
                    }
                } catch (IOException var18) {
                    try {
                        if (var2 != null) {
                            var2.close();
                        }

                        if (var3 != null) {
                            var3.close();
                        }
                    } catch (IOException var17) {
                    }

                    Initializer.this.ioe = var18;
                } finally {
                    try {
                        if (var1 != null) {
                            var1.close();
                        }
                    } catch (IOException var16) {
                    }

                }

            }
        }

1.2 this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);

在取得 Source 和Sink 的FD后,后面还有一步将 Source 端的FD 保存到pollWrapper中。


 		// 将source端的 fd 保存到 pollWrapper 中,默认兴趣事件ops 是 POLLIN, 即输入时间
        this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);

PollArrayWrapper#addWakeupSocket方法代码如下, 省略一些不必要的参数信息 :
可以看到 addWakeupSocket 方法调用了 putDescriptor 保存fd 和 putEventOps 保存 兴趣事件ops。经过这一步,即将 Source端的 FD 保存到 PollArray中。当Sink端写入数据时, Source端的FD就会处于就绪状态,Selector.select() 方法就会被唤醒,不再阻塞,这一点后面再讲。

    void addWakeupSocket(int var1, int var2) {
        this.putDescriptor(var2, var1);
        this.putEventOps(var2, Net.POLLIN);
    }
    // 保存文件描述符fd
    void putDescriptor(int var1, int var2) {
        this.pollArray.putInt(SIZE_POLLFD * var1 + 0, var2);
    }
	// 保存兴趣事件ops
    void putEventOps(int var1, int var2) {
        this.pollArray.putShort(SIZE_POLLFD * var1 + 4, (short)var2);
    }

2. Selector.register

Selector.open 方法中保存的Source 和Sink 的FD 和用户操作无关联,仅仅是为了实现wakeup的唤醒操作,通过 SelectableChannel#register(java.nio.channels.Selector, int) 注册的事件才是真正和用户相关的事件。

调用链如下:
SelectableChannel#register(java.nio.channels.Selector, int) -> AbstractSelectableChannel#register

从下面代码可以看到,流程如下

  1. register 方法中先对一些状态进行校验。
  2. 校验通过后,再通过 SelectionKey k = findKey(sel); 来判断当前channel是否已经注册在 Selector 上(可以看到是否注册是通过一个keys 是否包含来判断的。在后面的 implRegister 方法调用中会将注册的Channel 添加进去)。
  3. 如果注册了,则添加新的兴趣事件( k.interestOps(ops);)。
  4. 如果channel没有注册到当前选择器则调用 AbstractSelector#register 方法进行注册

下面从 AbstractSelectableChannel#register开始分析。

1. AbstractSelectableChannel#register

    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();
            // 查找是否已经注册过该 channel。
            SelectionKey k = findKey(sel);
            if (k != null) {
            	// 如果已经保存了,则在SelectionKey上绑定新的兴趣事件和附件对象。
                k.interestOps(ops);
                k.attach(att);
            }
            // 如果该channel 没有注册过,则进行注册
            if (k == null) {
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    // 注册一个新的 SelectionKey
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    // 并添加到SelectionKey[] keys 集合中汇总。
                    addKey(k);
                }
            }
            return k;
        }
    }


	... 
	// 添加SelectionKey到 set集合中。
    private void addKey(SelectionKey k) {
        assert Thread.holdsLock(keyLock);
        int i = 0;
        if ((keys != null) && (keyCount < keys.length)) {
            // Find empty element of key array
            for (i = 0; i < keys.length; i++)
                if (keys[i] == null)
                    break;
        } else if (keys == null) {
            keys =  new SelectionKey[3];
        } else {
       		// 扩容
            // Grow key array
            int n = keys.length * 2;
            SelectionKey[] ks =  new SelectionKey[n];
            for (i = 0; i < keys.length; i++)
                ks[i] = keys[i];
            keys = ks;
            i = keyCount;
        }
        keys[i] = k;
        keyCount++;
    }
    ...
	//
 	private SelectionKey findKey(Selector sel) {
        synchronized (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;
        }
    }

2 SelectorImpl#register

上面说到如果channel没有注册到当前选择器则调用 AbstractSelector#register 方法进行注册。那么继续分析AbstractSelector#register 方法。这里来看 AbstractSelector#register 的实现方法 SelectorImpl#register

可以看到 register方法流程如下:

  1. 创建一个 SelectionKeyImpl 对象,SelectionKeyImpl 对象中保存了当前channel和Selector的信息
    NIO 源码浅析_第1张图片
  2. 调用 implRegister(SelectionKeyImpl var1) 来进行channel的注册。这个后面详解
  3. 将兴趣事件ops 绑定到 SelectionKeyImpl 对象上
  4. 返回 SelectionKeyImpl 对象。
    protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
        if (!(var1 instanceof SelChImpl)) {
            throw new IllegalSelectorException();
        } else {
        	// 创建一个新的SelectionKeyImpl。绑定附件对象。
            SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
            var4.attach(var3);
            synchronized(this.publicKeys) {
            	// 注册文件描述符
                this.implRegister(var4);
            }
			// 注册兴趣事件
            var4.interestOps(var2);
            return var4;
        }
    }

核心方法在: WindowsSelectorImpl#implRegister
下面重点分析implRegister 方法:

3.WindowsSelectorImpl#implRegister

代码如下:

    protected void implRegister(SelectionKeyImpl var1) {
        synchronized(this.closeLock) {
            if (this.pollWrapper == null) {
                throw new ClosedSelectorException();
            } else {
            	// 如果需要,扩容pollWrapper
                this.growIfNeeded();
                // channelArray 保存当前 SelectionKeyImpl
                this.channelArray[this.totalChannels] = var1;
                var1.setIndex(this.totalChannels);
                this.fdMap.put(var1);
                // 添加将要注册的Channel信息,用来上面判断上面是否已经注册
                this.keys.add(var1);
                // 将新的SelectorKeyImpl 保存到 pollWrapper 上。
                this.pollWrapper.addEntry(this.totalChannels, var1);
                ++this.totalChannels;
            }
        }
    }

	...
	// growIfNeeded 做了两件事
	// 1. 扩容 channelArray 和 pollWrapper 数组大小
	// 2. 每当 totalChannels 数量达到1024个时,增加一个线程。windows上select系统调用有最大文件描述符限制,一次只能轮询1024个文件描述符,如果多于1024个,需要多线程进行轮询。
    private void growIfNeeded() {
        if (this.channelArray.length == this.totalChannels) {
            int var1 = this.totalChannels * 2;
            SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1];
            System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1);
            this.channelArray = var2;
            this.pollWrapper.grow(var1);
        }

        if (this.totalChannels % 1024 == 0) {
            this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels);
            ++this.totalChannels;
            ++this.threadsCount;
        }

    }

implRegister 重点的一句就是 this.pollWrapper.addEntry(this.totalChannels, var1); 。addEntry方法具体如下。我们可以看到,他将当前 SelectionKeyImpl 中保存的channel的FD保存到了 pollWrapper 中。这时候pollWrapper中现在就保存了Source 端的 FD 和当前用户 channel 的FD。

    void addEntry(int var1, SelectionKeyImpl var2) {
        this.putDescriptor(var1, var2.channel.getFDVal());
    }

1.3 SelectionKeyImpl#interestOps(int)

接下来看 interestOps 的是实现。如下:

SelectionKeyImpl#interestOps(int) -> SelectionKeyImpl#nioInterestOps(int) -> SocketChannelImpl#translateAndSetInterestOps(这一步里面有多个实现,这里列举其中一个实现,其余的都差不多)。
可以看到就是判断事件的类型,然后将兴趣事件保存到 pollWrapper 数组中。

public void translateAndSetInterestOps(int var1, SelectionKeyImpl var2) {
        int var3 = 0;
        if ((var1 & 1) != 0) {
            var3 |= Net.POLLIN;
        }

        if ((var1 & 4) != 0) {
            var3 |= Net.POLLOUT;
        }

        if ((var1 & 8) != 0) {
            var3 |= Net.POLLCONN;
        }

        var2.selector.putEventOps(var2, var3);
    }
	// 保存兴趣事件
    public void putEventOps(SelectionKeyImpl var1, int var2) {
        synchronized(this.closeLock) {
            if (this.pollWrapper == null) {
                throw new ClosedSelectorException();
            } else {
                int var4 = var1.getIndex();
                if (var4 == -1) {
                    throw new CancelledKeyException();
                } else {
                    this.pollWrapper.putEventOps(var4, var2);
                }
            }
        }
    }

3. SelectionKey.select()

上面分析完了Selector.open()Selector.register 的流程,下面来分析一下 SelectionKey.select() 方法的流程。
无论是select() 还是 select(long timeout) 亦或是 selectNow() 。最终都会调用 WindowsSelectorImpl#doSelect 方法。所以这里列举select() 方法为例。

调用链路如下: SelectorImpl#select() -> SelectorImpl#select(long) ->SelectorImpl#lockAndDoSelect -> SelectorImpl#doSelect -> WindowsSelectorImpl#doSelect

可以看到整个流程如下:

  1. 调用 processDeregisterQueue() 方法对已经取消的 keys 进行注销操作。通过调用cancel()方法将选择键加入已取消的键集合中,这个键并不会立即注销,而是在下一次select操作时进行注销。
  2. 如果中断操作,重置 WakeupSocket (这步不知道干啥)
  3. 如果不中断操作。调用 adjustThreadsCount () 方法调整守护线程数量,可能增加也可能减少(根据上面growIfNeededregister 注册的channel数量来判断 -> growIfNeeded方法中会根据注册的数量来调整线程数量 threadsCount)。
  4. this.subSelector.poll(); 调用本地方法 poll0 阻塞线程,当有兴趣事件就绪时,才会返回。
    protected int doSelect(long var1) throws IOException {
        if (this.channelArray == null) {
            throw new ClosedSelectorException();
        } else {
        	// 设置超时时间
            this.timeout = var1;
            // 对已取消的键集合进行处理,通过调用cancel()方法将选择键加入已取消的键集合中,这个键并不会立即注销,而是在下一次select操作时进行注销,注销操作在implDereg完成
            this.processDeregisterQueue();
            if (this.interruptTriggered) {
                this.resetWakeupSocket();
                return 0;
            } else {
            	// 必要情况下调整线程数量。在上面 growIfNeeded 方法中进行调整过 threadsCount 数量
                this.adjustThreadsCount();
                this.finishLock.reset();
                this.startLock.startThreads();

                try {
                    this.begin();

                    try {
					// 调用本地的native方法,将之前保存的的pollWrapper数据地址传递过去。这一步是关键
                        this.subSelector.poll();
                    } catch (IOException var7) {
                        this.finishLock.setException(var7);
                    }

                    if (this.threads.size() > 0) {
                        this.finishLock.waitForHelperThreads();
                    }
                } finally {
                    this.end();
                }

                this.finishLock.checkForException();
                this.processDeregisterQueue();
                int var3 = this.updateSelectedKeys();
                this.resetWakeupSocket();
                return var3;
            }
        }
    }

	...
	private void adjustThreadsCount() {
        int var1;
        if (this.threadsCount > this.threads.size()) {
            for(var1 = this.threads.size(); var1 < this.threadsCount; ++var1) {
                WindowsSelectorImpl.SelectThread var2 = new WindowsSelectorImpl.SelectThread(var1);
                this.threads.add(var2);
                var2.setDaemon(true);
                var2.start();
            }
        } else if (this.threadsCount < this.threads.size()) {
            for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
                ((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
            }
        }

    }

其中

  1. adjustThreadsCount() 方法中创建的线程可以看到是 WindowsSelectorImpl.SelectThread。即 WindowsSelectorImpl 的 内部类。其run方法如下。可以看到,新创建的线程会阻塞在 waitForStart() 方法上。当主线程调用 this.startLock.startThreads(); 时才会被唤醒,并且和主线程一起轮询就绪事件。

    		// SelectThread.run 方法。
            public void run() {
                for(; !WindowsSelectorImpl.this.startLock.waitForStart(this); WindowsSelectorImpl.this.finishLock.threadFinished()) {
                    try {
                        this.subSelector.poll(this.index);
                    } catch (IOException var2) {
                        WindowsSelectorImpl.this.finishLock.setException(var2);
                    }
                }
    
            }
    
    	... 
    	// waitForStart 方法,和 run方法并不在一个类中。
     	private synchronized boolean waitForStart(WindowsSelectorImpl.SelectThread var1) {
                while(this.runsCounter == var1.lastRun) {
                    try {
                        WindowsSelectorImpl.this.startLock.wait();
                    } catch (InterruptedException var3) {
                        Thread.currentThread().interrupt();
                    }
                }
    
                if (var1.isZombie()) {
                    return true;
                } else {
                    var1.lastRun = this.runsCounter;
                    return false;
                }
            }
    
  2. this.subSelector.poll() 调用本地方法poll0如下,poll0会阻塞。可以看到,poll0将 pollWrapper的地址传递过去。poll0方法会轮询 pollWrapper 中的channel的FD,并将结果记录在临时表中,当有兴趣事件就绪时,则会被唤醒,则会停止轮询并返回。

        	private final int[] readFds = new int [MAX_SELECTABLE_FDS + 1];//readFds保存发生read的FD
       	 	private final int[] writeFds = new int [MAX_SELECTABLE_FDS + 1];//writeFds保存发生写的FD
        	private final int[] exceptFds = new int [MAX_SELECTABLE_FDS + 1];//exceptFds保存发生except的FD
    
            private int poll() throws IOException {
                return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
            }
    

4. WindowsSelectorImpl#wakeup

wakeup用来立即唤醒select()线程。根据代码可以看到,最终调用了本地方法 setWakeupSocket0 。
其内部原理是,当用户调用wakeup方法时,系统会对Sink端写入一个字节,此时 Source端的FD就会处于就绪状态,根据上面介绍的select() 原理,当有就绪的事件时就会返回。

    public Selector wakeup() {
        synchronized(this.interruptLock) {
            if (!this.interruptTriggered) {
                this.setWakeupSocket();
                this.interruptTriggered = true;
            }

            return this;
        }
    }
	...
	   private void setWakeupSocket() {
        this.setWakeupSocket0(this.wakeupSinkFd);
    }
	...
	 private native void setWakeupSocket0(int var1);

以上:内容部分参考
http://www.myexception.cn/program/1598318.html
https://www.iteye.com/blog/goon-1775421
https://blog.csdn.net/aesop_wubo/article/details/9117655
https://blog.csdn.net/u010412719/article/details/52809669
https://blog.csdn.net/u010412719/article/details/52819191
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

你可能感兴趣的:(Java)