Selector.wakeup() 提供了使線程從被阻塞的select()方法中優雅的退出的能力
同樣有三種方式喚醒select()方法中睡眠的線程:
1 Selector.wakeup() 將使Selector上第一個沒有返回的select()操作立即返回,如果當前沒有正在進行的select()操作,則下次select()操作會立即返回。
2 Selector.close() 被調用時,所有在select()操作中阻塞的線程都將被喚醒,Selector相關通道被注銷,相關SelectionKey將被取消
3 Thread.interrupt()被調用時,返回狀態將被設置。如果喚醒之后的線程試圖在通道上執行IO操作,通道會立即關閉,線程捕捉到一個異常
如果想讓一個睡眠線程在中斷之后繼續進行,需要執行一些步驟來清理中斷狀態
這些操作不會改變單個相關通道,中斷一個Selector和中斷一個通道是不一樣的。Selector只會檢查通道的狀態。
當一個select() 操作時睡眠的線程發生了中斷,對于通道狀態而言,是沒有歧義的
選擇器把合理的管理SelectionKey,以確保它們表示的狀態不會變得陳舊的任務交給了程序員
當SelectionKey已經不在選擇器的Selected key set中時,會發生什么
當通道上至少一個感興趣的操作就緒時,SelectionKey的ready集合會被清空,并且當前已經就緒的操作會被添加到ready集合里,該SelectionKey隨后會被添加到Selected key set中
清理一個SelectionKey的ready集合的方式是將這個key從Selected key set中移除
SelectionKey的就緒狀態只有在Selector的select() 操作過程中才會修改,因為只有Selected key set中的SelectionKey才被認為是包含了合法的就緒信息的,這些信息在SelectionKey中長久存在,知道key從Selected key set中移除,以通知Selector你已經看到并對它進行了處理。當下一次通道的感興趣的操作發生時,key將被重新設置以反映當時通道的狀態,并再次被添加到Selected key set中
這種框架提供了非常大的靈活性。
常規做法是先調用select() 操作,再遍歷selectKeys()返回的key的集合。在按順序進行檢查每個key的過程中,相關的通道也根據key的就緒集合進行處理。然后key從Selected key set中移除,檢查下一個key。完成后通過調用select() 重復循環。
服務器端使用方法
1 創建ServerSocketChannel,注冊到Selector中,感興趣的操作為accept
2 輪詢Selector的select()操作,從就緒key集合中遍歷key的ready集合,有accept則調用ServerSocketChannel的accept()方法獲取SocketChannel,
其中包含接收到的socket的句柄。再將SocketChannel注冊到Selector中感興趣的操作為read
3 當下次select()操作中key的ready集合中有read時,開始做事
Selector是線程安全的,但key set不是。Selector.keys 和 Selector.selectkeys() 返回的是Selector內部私有key set的引用。這個集合可能隨時被改變。迭代器Iterator是快速失敗的(fail-fast),如果迭代的時候key set發生改變,拋出ConcurrentModificationException。所有如果希望在多個線程間共享Selector或SelectionKey,則要對此做好準備。當你修改一個key的時候,可能會破壞另一個線程的Iterator
如果在多個線程并發的訪問一個Selector的key set時,需要合理地同步訪問。在select()操作時,先在Selector上進行同步,再是Registered key set,最后是Selected key set。按照這樣的順序,Cancelled key set就會在第一步和第三步之間保持同步。
在多線程的場景中,如果需要對任何一個key set進行更改,不管是直接更改還是其他操作帶來的副作用,都需要以相同的順序,在同一對象上進行同步。如果競爭的線程沒有以同樣的順序請求鎖,則會有死鎖的隱患。
Selector的close()和select()操作都會有一直阻塞的可能,當在select()的過程當中,所有對close()的調用都會被阻塞,直到select()結束,或者執行select()的線程進入睡眠。當select()的線程進入睡眠時,close()的線程獲得鎖后會立即喚醒select()的線程,并關閉Selector
如果不采取相同的順序同步,則key set中key的信息不保證是有效的,相關通道也不保證是打開的
一個cpu的時候使用一個線程來管理所有通道,是一個合適的解決方案,但會浪費其他cpu的運行能力
在大量通道上執行select()操作不會有太大開銷,因為大多數工作都是操縱系統完成的
方案1 管理多個Selector,并隨機分配通道不是一個好方案
方案2 所有通道使用一個Selector,將就緒通道的服務委托給其他線程。相當于用一個線程監控通道的就緒狀態,使用另外的工作線程池來處理接收到的數據。而線程池是可以調整的,或者動態調整。
在方案2中,如果某些通道要求更高的響應速度,可以用兩個選擇器來解決。并且線程池可以細化為日志線程池、命令線程池、狀態請求線程池等