Netty進階:Netty核心NioEventLoop原理解析
Netty提供了Java NIO Reactor模型的實現,之前寫過一篇文章是對三種Reactor模式的簡單實現:Reactor模型的Java NIO實現,當然netty中的實現要複雜的多。並且Netty將實現好的Reactor模型封裝起來,我們只需提供適當的引數就可以實現不同執行緒模式的Reactor模型。
單執行緒模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup)
例項化了一個 NioEventLoopGroup, 構造器引數是1, 表示 NioEventLoopGroup 的執行緒池大小是1. 然後接著我們呼叫 b.group(bossGroup) 設定了伺服器端的 EventLoopGroup。
多執行緒模型
EventLoopGroup bossGroup = new NioEventLoopGroup(n);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
主從多執行緒模型
EventLoopGroup bossGroup = new NioEventLoopGroup(4);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group (bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
Netty 的伺服器端的 acceptor 階段, 沒有使用到多執行緒,也就是說n設定1和10是沒有區別的。
伺服器端的 ServerSocketChannel 只繫結到了 bossGroup 中的一個執行緒, 因此在呼叫 Java NIO 的 Selector.select 處理客戶端的連線請求時, 實際上是在一個執行緒中的, 所以對只有一個服務的應用來說, bossGroup 設定多個執行緒是沒有什麼作用的, 反而還會造成資源浪費。
1. NioEventLoopGroup
NioEventLoopGroup 類層次結構:
下面分析例項化過程,NioEventLoop 有幾個過載的構造器, 不過內容都沒有什麼大的區別, 最終都是呼叫的父類MultithreadEventLoopGroup構造器:
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
此時個引數值對應如下:
nThreads:0
executor: null
selectorProvider: SelectorProvider.provider()
selectStrategyFactory: DefaultSelectStrategyFactory.INSTANCE
拒絕策略RejectedExecutionHandlers.reject()
如果我們傳入的執行緒數 nThreads 是0, 那麼 Netty 會為我們設定預設的執行緒數 DEFAULT_EVENT_LOOP_THREADS, 而這個預設的執行緒數是怎麼確定的呢?其實很簡單, 在靜態程式碼塊中, 會首先確定 DEFAULT_EVENT_LOOP_THREADS 的值:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
回到MultithreadEventLoopGroup構造器中, 這個構造器會繼續呼叫父類 MultithreadEventExecutorGroup 的構造器:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
在此構造方法中,我們指定了一個EventExecutor的選擇工廠DefaultEventExecutorChooserFactory,此工廠主要是用於選擇下一個可用的EventExecutor, 其內部有兩種選擇器, 一個是基於位運算,一個是基於普通的輪詢,它們的程式碼分別如下:
基於位運算的選擇器PowerOfTwoEventExecutorChooser
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
基於普通輪詢的選擇器GenericEventExecutorChooser
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
我們知道, MultithreadEventExecutorGroup 內部維護了一個 EventExecutor 陣列, Netty 的 EventLoopGroup 的實現機制其實就建立在 MultithreadEventExecutorGroup 之上。 每當 Netty 需要一個 EventLoop 時, 會呼叫EventExecutorChooser的next() 方法獲取一個可用的 EventLoop。
繼續回到MultithreadEventExecutorGroup的構造器:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 初始化executor
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// 初始化EventExecutor
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
children[i] = newChild(executor, args);
success = true;
}
// 生成選擇器物件
chooser = chooserFactory.newChooser(children);
}
此構造方法主要做了三件事:
- 初始化executor為ThreadPerTaskExecutor的例項
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
ThreadPerTaskExecutor 實現了Executor介面,其內部會通過newDefaultThreadFactory()指定的預設執行緒工廠來建立執行緒,並執行相應的任務。
2. 建立一個大小為 nThreads的EventExecutor陣列children,然後為每一個數組元素建立EventExecutor。children[i] = newChild(executor, args);
, newChild(executor, args)方法在MultithreadEventExecutorGroup中沒有實現,我們在NioEventLoopGroup中找到了newChild的實現:
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
就是建立NioEventLoop物件,關於NioEventLoop在後面詳細介紹。
- 根據我們預設DefaultEventExecutorChooserFactory選擇器工廠,繫結NioEventLoop陣列物件。在前面的構造方法中,我們指定了chooserFactory為DefaultEventExecutorChooserFactory,在此工廠內部,會根據children陣列的長度來動態選擇選擇器物件,用於選擇下一個可執行的EventExecutor,也就是NioEventLoop。
總結一下整個 NioEventLoopGroup 的初始化過程:
-
NioEventLoopGroup(其實是MultithreadEventExecutorGroup) 內部維護一個型別為 EventExecutor children 陣列, 其大小是 nThreads, 這樣就構成了一個執行緒池
-
如果我們在例項化 NioEventLoopGroup 時, 如果指定執行緒池大小, 則 nThreads 就是指定的值, 反之是處理器核心數 * 2
-
MultithreadEventExecutorGroup 中會呼叫 newChild 抽象方法來初始化 children 陣列。抽象方法 newChild 是在 NioEventLoopGroup 中實現的, 它返回一個 NioEventLoop 例項.
最後以一張圖來總結:
2. NioEventLoop
前面一節說道了newChild建立NioEventLoop例項,下面開始分析NioEventLoop。
NioEventLoop 繼承於 SingleThreadEventLoop, 而 SingleThreadEventLoop 又繼承於 SingleThreadEventExecutor. SingleThreadEventExecutor 是 Netty 中對本地執行緒的抽象, 它內部有一個 Thread thread 屬性, 儲存了一個本地 Java 執行緒. 因此我們可以認為, 一個 NioEventLoop 其實和一個特定的執行緒繫結, 並且在其生命週期內, 繫結的執行緒都不會再改變。
NioEventLoop的父類以及介面比較多,但是隻需要關注幾個重要的:
- SingleThreadEventLoop
- SingleThreadEventExecutor :實現任務佇列
- AbstractScheduledEventExecutor:主要是實現定時任務
2.1例項化過程
在newChild方法中,呼叫了下面NioEventLoop建構函式:
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
provider = selectorProvider;//1
final SelectorTuple selectorTuple = openSelector();//2
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;//3
}
- 在初始化NioEventLoopGroup的時候就已經用到了SelectorProvider這個引數了,SelectorProvider是Java NIO中的抽象類,它的作用是呼叫Windows或者Linux底層NIO的實現,為JavaNIO提供服務,比如經常用的Selector.open()方法內部就是通過呼叫SelectorProvider.openSelector()來得到多路複用器selector。在這裡賦值給NioEventLoop的provider屬性。
- SelectorTuple是NioEventLoop的內部類,其實就是對Java NIO Selector的封裝。
private static final class SelectorTuple {
final Selector unwrappedSelector;
final Selector selector;
//省略建構函式
}
selector和unwrappedSelector分別表示優化過的Selector和未優化過的Selector,selectedKeys表示優化過的SelectionKey。Netty在該類中對Java NIO的Selector做了優化,可以通過設定系統屬性io.netty.noKeySetOptimization進行修改,設定為true、yes或者1關閉優化,設定為false、no或者0開啟優化,預設開啟優化。
3. TODO
接下里進入父類SingleThreadEventLoop的建構函式super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler)
:
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
tailTasks = newTaskQueue(maxPendingTasks);
}
在此建構函式中只是初始化了TaskQueue(內部為有界的LinkedBlockingQueue),長度預設為DEFAULT_MAX_PENDING_TASKS,該常量定義於SingleThreadEventLoop類中,預設為16。繼續看父類SingleThreadEventExecutor的建構函式:
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ObjectUtil.checkNotNull(executor, "executor");
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
executor即執行緒池,還記得上節講的初始化NioEventLoopGroup麼,在MultithreadEventExecutorGroup建構函式中執行executor = new ThreadPerTaskExecutor(newDefaultThreadFactory())
,一邊儲存在類屬性中,一邊傳入了newChild方法中,最終也傳入該建構函式。
並且在SingleThreadEventExecutor類中有一個屬性private volatile Thread thread
,它用來引用支撐該EventExecutor的執行緒,用來處理I/O事件和執行任務,叫支撐執行緒或者I/O執行緒均可,thread所引用的執行緒即來自executor。
這裡也初始化了taskQueue,它是任務佇列用來存放需要排程執行的任務,其中tailTasks和taskQueue均是任務佇列,而優先順序不同,taskQueue的優先順序高於tailTasks,定時任務的優先順序高於taskQueue。
宣告一點:
本文中使用的是Netty4.1.22版本,相比之前的版本,這裡對執行緒模型做了一些小改動。Thread thread屬性應用了執行緒池中的執行緒,也就是execute中的執行緒,而在舊版本中是指向獨立的執行緒,並且是通過執行緒工廠建立的。舊版本如下
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
this.parent = parent;
this.addTaskWakesUp = addTaskWakesUp;
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
updateLastExecutionTime();
SingleThreadEventExecutor.this.run();
success = true;
}
});
threadProperties = new DefaultThreadProperties(thread);
taskQueue = newTaskQueue();
}
2.2 Netty 對Selecter的優化
在NioEventLoop例項化的過程中有提到,Netty對JDK Selector的優化,其實主要是對SelectKeys進行優化,JDK NIO中比如獲取準備好的key通過如下程式碼:
Set<SelectionKey> keys = selector.selectedKeys();
那麼返回的是Set介面的實現HashSet,SeletctorImp中的定義如下。
protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();
每當向Selector註冊時,對自動向key中新增元素,呼叫select方法後會更新該集合。Netty實現了AbstractSet,提供了和HashSet同樣的方法,只是內部實現不同(HashSet內部是HashMap),至於為什麼要這樣做,本人認為1.省去了Map中的value物件的記憶體,2.更方便的擴容。那麼Netty是如何做到的?其實就是在NioEventLoop例項的過程中呼叫的openSelector方法內部。
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
unwrappedSelector = provider.openSelector();
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
//省去幹擾程式碼
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
}
});
selectedKeys = selectedKeySet;
}
先是通過相應平臺的的Epoll實現,建立Selector物件,然後構造一個SelectedSelectionKeySet物件,這是Netty自己對SelectKeys的實現,然後通過反射將Selector物件中的selectedKeySet成員變數替換為自己的實現。此處應該全體起立喊666,哈哈!更絕的是Netty最新的4.x版本中加了一條:如果JDK版本大於等於9,連反射都不用了,直接通過CAS操作,通過成員變數的的偏移地址修改。有興趣可以把程式碼拉下來參觀參觀。
2.3 關聯EventLoop
前面說完了EventLoopGroup的例項化(包括EventLoop),也就是EventLoopGroup bossGroup = new NioEventLoopGroup(n)
這行程式碼。接下來在group方法將BootStrap和EventLoopgroup關聯,下面主要分析channel關聯EventLoop。
channel關聯Eventloop有三種情況:客戶端SocketChannel關聯EventLoop、服務端ServerSocketChannel關聯EventLoop、由服務端ServerSocketChannel建立的SocketChannel關聯EventLoop。Netty厲害的就是把這三種情況都都能複用Multithread EventLoopGroup中的registe方法:
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
根據選擇策略找到可用的EventLoop,然後呼叫SingleThreadEventLoop中的register方法,最終呼叫了 AbstractChannel#AbstractUnsafe.register 後
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 刪除條件檢查.
...
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new OneTimeTask() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
...
}
}
}
將一個 EventLoop 賦值給 AbstractChannel 內部的 eventLoop 欄位, 到這裡就完成了 EventLoop 與 Channel 的關聯過程。
但是上面程式碼一直是bind過程,也及時說在main執行緒中執行,所以會跳入else分支。將註冊操作包裝為一個Runnable,提交給eventloop的execute方法,該方法實現是在SingleThreadEventExecutor中實現的。
總結一下NioEventLoop,NioEventLoop是對IO 操作和執行緒的整合,那麼什麼時候啟動這個執行緒?答案是註冊Channel(向selector註冊)的時候。這是Server端的第一步,因此在這裡啟動執行緒比較合適。
下面分析NoioEventLoop的任務處理機制。
3. NioEventLoop的任務處理機制
一個 NioEventLoop 通常需要肩負起兩種任務, 第一個是作為 IO 任務, 處理 IO 操作,如accept、connect、read、write等。第二個就是非IO任務, 處理 taskQueue 中的任務,,如register0、bind0等任務。
Java NIO流程
回顧一下NIO中Selector的使用流程:
-
通過 Selector.open() 開啟一個 Selector.
-
將 Channel 註冊到 Selector 中, 並設定需要監聽的事件(interest set)
-
不斷重複:
3.1 呼叫 select() 方法
3.2 呼叫 selector.selectedKeys() 獲取 selected keys
3.3迭代每個 selected key:
3.4 從 selected key 中獲取 對應的 Channel 和附加資訊(如果有的話)
3.5 判斷是哪些 IO 事件已經就緒了, 然後處理它們. 如果是 OP_ACCEPT 事件, 則呼叫 “SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()” 獲取 SocketChannel, 並將它設定為 非阻塞的, 然後將這個 Channel 註冊到 Selector 中.
3.6 根據需要更改 selected key 的監聽事件.
3.7將已經處理過的 key 從 selected keys 集合中刪除
3.1 register任務
第一步開啟Selector,在例項化NioEventLoop時已經初始化完成,也儲存至NioEventLoop的屬性selector中。將channel註冊到selector中通過上面register0(promise)
來完成,回顧一下注冊的呼叫鏈:
Bootstrap.initAndRegister ->
AbstractBootstrap.initAndRegister ->
MultithreadEventLoopGroup.register ->
SingleThreadEventLoop.register ->
AbstractUnsafe.register -> //交給EventLoop執行
AbstractUnsafe.register0 ->
AbstractNioChannel.doRegister
register0 又呼叫了 AbstractNioChannel.doRegister:
@Override
protected void doRegister() throws Exception {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
}
在這裡 javaChannel() 返回的是一個 Java NIO SocketChannel 物件, 我們將此 SocketChannel 註冊到 Selector 中。第二個是感興趣的事件,0表示對所有事件都不感興趣。這裡只是將Channel註冊到Selecto。設定感興趣的事件在ChannelActive事件中實現,具體的說是在HeadContet中實現的。具體邏輯請看https://blog.csdn.net/TheLudlows/article/details/82712942 。第三個引數this,是attch物件。相當於把當前的NioServerScoketChannel(Netty對JDK原生ServerSocketChannel的包裝)放進去。這個會在後面的IO處理中用到。
3.2 啟動執行緒,新增任務
在3.2節中所講的只是channel註冊任務,netty會把此任務提交給任務佇列,通過execute方法去啟動執行緒。同時,selector的操作流程只完成了兩步,帶著一個問題:還有最後一步迴圈呼叫select方法在哪完成的呢?execute方法實現了Java併發包Executor的介面方法,用來執行任務。
@Override
public void execute(Runnable task) {
//此處為false,因為還在main執行緒中
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
如果當前執行的執行緒不是所繫結的執行緒則呼叫startThread方法為本EventExecutor繫結支撐執行緒,然後嘗試啟動執行緒(但由於執行緒是單個的,因此只能啟動一次),隨後再將任務新增到佇列中去。如果執行緒已經停止,並且刪除任務失敗,則執行拒絕策略,預設是丟擲異常。
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
SingleThreadEventExecutor.this.run();
success = true;
//省略部分程式碼
}
}
如果EventExecutor的狀態為ST_NOT_STARTED,那麼先修改狀態然後呼叫doStartThread方法為本EventExecutor繫結執行緒,斷言指出此時thread必須為null,表明當前EventExecutor還未繫結任何執行緒。繫結任務交由執行緒池排程執行,執行緒池中執行該任務的執行緒被繫結到EventExecutor上。然後設定最後一次的執行時間。執行當前 NioEventLoop 的 run 方法,注意:這個方法是個死迴圈,是整個 EventLoop 的核心。
@Override
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()
相關推薦
Netty進階:Netty核心NioEventLoop原理解析
Netty提供了Java NIO Reactor模型的實現,之前寫過一篇文章是對三種Reactor模式的簡單實現:Reactor模型的Java NIO實現,當然netty中的實現要複雜的多。並且Netty將實現好的Reactor模型封裝起來,我們只需提供適當的
Netty進階:Futrue&Promise原始碼解析
文章目錄
1. Future&Promise
2. AbstractFuture
3.Completefuture
4.Channelfuture&Completechannel
Netty進階:Pilpeline原理分析
在上篇文章中提到每個SocketCahnnel或者ServerSocketChannel的父類AbstractChannel的建構函式中會例項化DefaultChannelPipeline。在本文中會詳細的介紹ChannelPiple例項化過程中的細節、以及Ch
Netty進階:客戶端連線原始碼分析
1. BootStrap啟動程式碼
客戶端方面的程式碼開始
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.gr
Netty 進階:write流程全解析
1. ChannelOutboundBuffer
1.1 ChannelOutboundBuffer概述
在分析write前,有必要介紹一下ChannelOutboundBuffer,顧名思義該類是用來快取寫向Socket的資料。每個 ChannelSocke
Netty進階:淺析Read事件
上篇文章講述了Accept事件的處理過程,本文將詳細分析Read過程中的細節。按照accept事件的思路,當讀事件進來的時候,會呼叫 unsafe 的 read 方法,這個方法的主要作用是讀取 Socket 緩衝區的記憶體,幷包裝成 Netty 的 ByteB
Trie樹進階:Double-Array Trie原理及狀態轉移過程詳解
前言: Trie樹本身就是一個很迷人的資料結構,何況是其改進的方案。 在本部落格中我會從DAT(Double-Array Tire)的原理開始,並結合其原始碼對DAT的狀態轉移過程進行解析。如果因此
薦書 | Netty進階之路:跟著案例學Netty
內容精選自 1000 多個一線業務實際案例真正從原理到實踐全景式講解 Netty 專案實踐快速領
Docker映象進階:瞭解其背後的技術原理
什麼是 docker 映象
docker 映象是一個只讀的 docker 容器模板,含有啟動 docker 容器
Android進階:六、在子線程中直接使用 Toast 及其原理
我的文章 public str toast 技術交流群 ui控件 及其 handle android進階 一般我們都把Toast當做一個UI控件在主線程顯示。但是有時候非想在子線程中顯示Toast,就會使用Handler切換到主線程顯示。
但是子線程中真的不能直接顯示Toa
Netty進階基礎篇之流概念篇(1)
序言:學Netty之前,對流的概念應該深刻一些,所以先理解一下流的使用!
1、IO、NIO、AIO 含義區別
1.1 同步
Netty進階基礎篇之Buffer篇(2)
1、Buffer概念
1.1 緩衝區獲取
Buffer緩衝區是就是一個數組,有著不同的資料型別:ByteBuffer、Ch
Netty進階基礎篇之NIO Channel篇(4)
1、Channel概念
通道(Channel):用於源節點與目標節點的連線。在 Java NIO 中負責緩衝區中資料的傳輸。C
Netty進階基礎篇之NIO 非阻塞通訊(6)
1、Tcp網路非阻塞通訊
//客戶端
@Test
public void client() throws IOExcept
Netty進階篇之簡單版websocket發訊息(7)
序言:簡單開發前端傳送訊息,後端接收訊息並返回。
1、故事牽引
今天通過一個故事來講解netty,主要講client和ser
Netty進階篇之websocket傳送訊息(8)
序言:Netty進階篇之簡單版websocket發訊息(7) 大概和下面的程式碼就成相似,如果不懂,可以看一下這篇部落格
【學習筆記】String進階:StringBuffer類(線程安全)和StringBuilder類
n) static this util double 字符串 對象 ice 單線程 一、除了使用String類存儲字符串之外,還可以使用StringBuffer類存儲字符串。而且它是比String類更高效的存儲字符串的一種引用數據類型。
優點:
對字符串進行連接操作時,
python開發函數進階:命名空間,作用域,函數的本質,閉包,內置方法(globales)
問題 總結 加載 自己的 ger 作用域 範圍 沒有 概念 一,命名空間
#局部命名空間#全局命名空間#內置命名空間
#三者的順序#加載順序 硬盤上——內存裏#內置-->全局(從上到下順序加載進來的)-->局部(調用的時候加載)
1 #!/usr/bin/
python開發函數進階:裝飾器
for 中國 eas login please 函數 功能 log 原則 一,裝飾器本質
閉包函數
功能:就是在不改變原函數調用方式的情況下,在這個函數前後加上擴展功能
二,設計模式
開放封閉原則
*對擴展是開放的
*對修改是封閉的
三,代碼解釋
1 #!/
[您有新的未分配科技點]博弈論進階:似乎不那麽恐懼了…… (SJ定理,簡單的基礎模型)
裏的 如果 cnblogs 經典 ant 控制 nim osi 取石子 這次,我們來繼續學習博弈論的知識。今天我們會學習更多的基礎模型,以及SJ定理的應用。
首先,我們來看博弈論在DAG上的應用。首先來看一個小例子:在一個有向無環圖中,有一個棋子從某一個點開始一直向它的出點