Netty 4 源碼分析——EventExecutor

先從EventExecutor開始,因爲它是一個很基礎的工具類,是對I/O線程的包裝。先了解下它的源碼會對後面的分析有更好的理解。 



先看下EventExecutor的類關係圖,這裏只是簡單的畫出了類和接口的繼承和實現關係,還有其他的聚合關係沒有畫出來,爲的是便於分析思路的清晰。 

說到Executor,很容易聯想到jdk中 java.util.concurrent.Executor 接口,這個接口非常簡單,就一個方法 

void execute(Runnable command);

從方法簽名上就能看出這個是爲了支持異步模式的。command表示一個命令。當前線程就是命令者角色,Executor內部的去運行Runnable的線程就是執行者。這裏沒有提供明確的地方供命令者去取得命令的執行結果。 

ExecutorService 繼承了Executor 接口,增加了對自身生命週期管理的方法,同時提供了一個Future給命令者去獲取命令的執行結果。 

ScheduledExecutorService 繼承了ExecutorService接口,增加了對定時任務的支持。 

EventExecutorGroup 繼承了ScheduledExecutorService接口,對原來的ExecutorService的關閉接口提供了增強,提供了優雅的關閉接口。從接口名稱上可以看出它是對多個EventExecutor的集合,提供了對多個EventExecutor的迭代訪問接口。 

EventExecutor 繼承EventExecutorGroup 看着這個關係真心有些糾結啊。不過細想下還是能理解的。A是B中的一員,但是A也能迭代訪問B中的其他成員。這個繼承關係支持了迭代訪問這個行爲。自然的他提供了一個parent接口,來獲取所屬的EventExecutorGroup 。另外提供了inEventLoop 方法支持查詢某個線程是否在EventExecutor所管理的線程中。還有其他一些創建Promise和Future的方法。 

AbstractEventExecutor 只是對EventExecutor中某些方法的簡單實現 

下面重點分析下非常有意思SingleThreadEventExecutor,它也是個抽象類,但是提供了很多重要方法的實現。弄清楚了這個對整個EventExecutor體系都非常有幫助。從類名上可知裏面只有一個線程,先搞清楚一個線程的處理過程再理解多線程的就輕鬆些了。 

先從execute方法入口分析 

@Override
public void execute(Runnable task) {
	if (task == null) {
		throw new NullPointerException("task");
	}

	boolean inEventLoop = inEventLoop();
	if (inEventLoop) {
		addTask(task);
	} else {
		startThread();
		addTask(task);
		if (isShutdown() && removeTask(task)) {
			reject();
		}
	}

	if (!addTaskWakesUp) {
		wakeup(inEventLoop);
	}
}
先不看這個源碼,我們分析下,作爲一個Executor,A讓你執行命令A,B讓你執行命令B。。。命令不是說執行就能執行的吧。總得有個地方保存還沒來得及執行的命令吧,總得有個先來後到吧。而這個地方又會被多線程訪問,得保證多線程訪問可見性,操作的原子性。jdk中提供的BlockingQueue就是爲此而生的。BlockingQueue是一個接口,jdk中有很多他的實現。SingleThreadEventExcutor中提供的這個地方叫tasksQueue,類型是jdk中的LinkedBlockingQueue。上面代碼中的addTask就是往tasksQueue中添加。 

另外SingleThreadEventExcutor實現了ScheduledExecutorService 接口,支持執行定時任務。得有個地方存放定時任務信息。類中的實現是delayedTaskQueue,它是一個PriorityQueue ,也是一個BlockingQueue。不過它裏面的元素不是按照先來後到的順序存取的,而是按照各個元素的優先級判斷的。 

因爲這個execute方法,是可以在外部線程調用,也可以在內部線程調用。也就是說外部成員可以給你下命令,內部成員也可以給你下命令。所以在上面的代碼中先調用inEventLoop判斷當前下命令的是外部的還是內部的。 
如果是外部的,先確定內部線程是否啓動,沒啓動就先啓動內部線程同時給自己加一個定時清理的定時任務。這個從下面的代碼中可以看出 

private void startThread() {
	synchronized (stateLock) {
		if (state == ST_NOT_STARTED) {
			state = ST_STARTED;
			delayedTaskQueue.add(new ScheduledFutureTask<Void>(
					this, delayedTaskQueue, Executors.<Void>callable(new PurgeTask(), null),
					ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL));
			thread.start();
		}
	}
}

private final class PurgeTask implements Runnable {
	@Override
	public void run() {
		Iterator<ScheduledFutureTask<?>> i = delayedTaskQueue.iterator();
		while (i.hasNext()) {
			ScheduledFutureTask<?> task = i.next();
			if (task.isCancelled()) {
				i.remove();
			}
		}
	}
}
添加命令完成了,下面就看如何去執行命令了,這個就需要分析下內部線程的執行邏輯了。SingleThreadEventExecutor類中有一個實力變量Thread,它引用的就是當前Executor所擁有的那個thread對象。 
thread = threadFactory.newThread(new Runnable() {
	@Override
	public void run() {
		boolean success = false;
		updateLastExecutionTime();
		try {
			SingleThreadEventExecutor.this.run();
			success = true;
		} catch (Throwable t) {
			logger.warn("Unexpected exception from an event executor: ", t);
		} finally {
			// 更改狀態
			if (state < ST_SHUTTING_DOWN) {
				state = ST_SHUTTING_DOWN;
			}

			// Check if confirmShutdown() was called at the end of the loop.
			// 這裏說明在try塊中調用的SingleThreadEventExecutor.this.run();中在方法結束之前必須調用confirmShutdown方法,這個在其之類實現的run方法中得到驗證
			if (success && gracefulShutdownStartTime == 0) {
				logger.error(
						"Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
						SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
						"before run() implementation terminates.");
			}

			try {
				// Run all remaining tasks and shutdown hooks.
				// 確保tasksQueue和shutDownHooks中的runable都處理完成了,這裏的處理完成有可能是超時了
				for (;;) {
					if (confirmShutdown()) {
						break;
					}
				}
			} finally {
				try {
					cleanup();
				} finally {
					synchronized (stateLock) {
						state = ST_TERMINATED;
					}
					//釋放信號量,使用Semaphore(0)來讓另一個線程一直等待,知道內部線程調用了release()
					threadLock.release();
					if (!taskQueue.isEmpty()) {
							logger.warn(
									"An event executor terminated with " +
									"non-empty task queue (" + taskQueue.size() + ')');
						}

						terminationFuture.setSuccess(null);
					}
				}
			}
		}
	});

上面Thread內部run方法執行的是SingleThreadEventExecutor.this.run(),而這個run方法是一個抽象方法,留給了子類去實現了。不過可以肯定的是子類的run方法是不斷的去tasksQueue中取出task去執行。現在重點分析下finally塊中的代碼。 
1、首先更改狀態爲正在關閉狀態。 
2、如果子類中的run方法中的loop執行成功了,就得先調用confirmShutdown,確認任務隊列中的任務是否都已經被執行了。 
3、然後還得再次確認下任務隊列中是否已被執行完畢,因爲在關閉的過程中外部也是能添加任務的。 
4、最終執行清理工作,更改狀態爲已關閉,釋放信號量。 
5、如果這個時候還是有任務沒執行完,那也只能是無奈了,記個log吧 
6、更新整個關閉過程爲success 

再分析下confirmShutdown,看看是如何保證所有的task執行完成的呢 
protected boolean confirmShutdown() {
	// 如果state狀態 state < ST_SHUTTING_DOWN則直接return false
	if (!isShuttingDown()) {
		return false;
	}
	// 這個方法必須從內部調用,從修飾符 protected也可以看出
	if (!inEventLoop()) {
		throw new IllegalStateException("must be invoked from an event loop");
	}
	// 取消所有的定時任務
	cancelDelayedTasks();
	
	if (gracefulShutdownStartTime == 0) {
		// 標記shutdown處理的開始時間
		gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
	}
	// 運行tasksQueue或者shutdownHooks中的所有Runnable都處理完成
	if (runAllTasks() || runShutdownHooks()) {
		//分析了下源碼,isShutdown()這個只能是在外部線程調用了shutdown()接口的時候纔會有可能成爲true
		//但是現在這個方法已經@Deprecated,所以這個if塊是不會進入的
		if (isShutdown()) {
			// shutdown 成功,沒有更多的runnable需要執行
			return true;
		}

		// There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period.
		wakeup(true);
		return false;
	}

	final long nanoTime = ScheduledFutureTask.nanoTime();
	// runAllTasks() 或者runAllTasks() + runShutdownHooks()方法執行時間操作了最大限制
	if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
		return true;
	}
	// 現在時間與上個任務執行完成的時間差小於quietPeriod時間,繼續檢測
	if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
		// Check if any tasks were added to the queue every 100ms.
		// TODO: Change the behavior of takeTask() so that it returns on timeout.
		wakeup(true);
		try {
			//內部線程sleep 100ms
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// Ignore
		}

		return false;
	}

	// No tasks were added for last quiet period - hopefully safe to shut down.
	// (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
	return true;
}
後面再看看這個類中的一些內部變量 
private static final Runnable WAKEUP_TASK = new Runnable() {
	@Override
	public void run() {
		// Do nothing.
	}
};
這個WAKEUP_TASK什麼也不做,爲啥取名wakeup呢?我看源碼也沒太明白。有人理解的給解釋下吧 

private final Semaphore threadLock = new Semaphore(0);
threadLock的內部permits設置爲0,也就是說acquire()永遠獲取不到permit,會一直被阻塞着。那有什麼用呢?另一種實現wait()/notify()吧 

值得注意的是類中是如何來控制定時任務的呢?祕密在這個方法中 

private void fetchFromDelayedQueue() {
	long nanoTime = 0L;
	for (;;) {
		ScheduledFutureTask<?> delayedTask = delayedTaskQueue.peek();
		if (delayedTask == null) {
			break;
		}

		if (nanoTime == 0L) {
			nanoTime = ScheduledFutureTask.nanoTime();
		}

		if (delayedTask.deadlineNanos() <= nanoTime) {
			delayedTaskQueue.remove();
			taskQueue.add(delayedTask);
		} else {
			break;
		}
	}
}

ScheduledFutureTask類中有個變量記錄這個類被加載進內存中的時間 

private static final long START_TIME = System.nanoTime();

static long nanoTime() {
	return System.nanoTime() - START_TIME;
}
// 返回到期時間,到期時間在構造函數中指定了
public long deadlineNanos() {
	return deadlineNanos;
}
// 這個方法決定了排序的優先級
@Override
public int compareTo(Delayed o) {
	if (this == o) {
		return 0;
	}

	ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
	long d = deadlineNanos() - that.deadlineNanos();
	if (d < 0) {
		return -1;
	} else if (d > 0) {
		return 1;
	} else if (id < that.id) {
		return -1;
	} else if (id == that.id) {
		throw new Error();
	} else {
		return 1;
	}
}
所以fetchFromDelayedQueue()方法的邏輯就是先取出即將到期的task,判斷是否已經到期,若已經到期就加入到tasksQueue中,等到被執行。 

先分析到這裏,後面在補上。 
有什麼不正確的,也請大家指正。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章