前段時間咱們遊戲server升級到開發環境Java8,這些天,我再次server的線程模型再次設計了一下,耗費Lambda表情。java
LambdaJava代碼。特別是醜陋不堪的匿名內部類,這篇文章主要就是想和你們分享這一點。數據庫
首先簡介一下咱們遊戲server的線程模型。大體例如如下圖所看到的:網絡
Netty線程池僅僅處理消息的收發,當Netty收到消息以後。會交給遊戲邏輯線程處理。由於是單線程在處理遊戲邏輯,因此每一個消息必須很是快處理完。也就是說,不能有數據庫等耗時操做。否則邏輯線程很是可能會被卡住。爲了避免卡住邏輯線程,數據庫操做由單獨的線程池來處理。異步
邏輯線程發起數據庫操做後便立刻返回繼續處理其它消息。數據庫線程池處理完成後。再通知邏輯線程,從而達到了異步數據庫操做的效果。ide
Netty部分的網絡代碼,在收到消息後。會依據消息找到相應的Action,而後運行。函數
詳細代碼省略,如下是簡化後的GameAction的代碼:線程
public abstract class GameAction { /** * 在邏輯線程裏處理消息. * @param gs * @param req 請求消息 */ public void execute(GameSession gs, Object req) { GameLogicExecutor.execute(() -> { doExecute(gs, req); }); } // 子類實現 public abstract void doExecute(GameSession gs, Object req); }execute()方法裏,使用了Lambda表達式來實現Runnable接口。
GameLogicExecutor是遊戲邏輯運行線程,代碼例如如下所看到的:設計
import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 遊戲邏輯線程. */ public class GameLogicExecutor { // todo 限制隊列長度 private static final Executor executor = Executors.newSingleThreadExecutor(); /** * 把遊戲邏輯放到隊列. * @param gameLogic */ public static void execute(Runnable gameLogic) { executor.execute(gameLogic); } }
如下看一個詳細的GameAction實現,這個Action依據用戶ID返回用戶建立的玩家列表。代碼例如如下:code
import java.util.List; public class GetPlayerListAction extends GameAction { @Override public void doExecute(GameSession gs, Object req) { int userId = (Integer) req; PlayerDao.getPlayerList(userId, (List<Player> players) -> { gs.write(players); }); } }若是請求參數是玩家ID。doExecute()方法並無等待數據庫操做。而是當即就返回了。傳遞給DAO的回調對象是個Lambda表達式,在回調方法裏,玩家列表經過GameSession被寫到client( 這僅僅是演示,實際的響應消息多是protobuf或JSON)。
import java.util.ArrayList; import java.util.List; public class PlayerDao { public static void getPlayerList(int userId, DbOpCallback<List<Player>> cb) { DbOpExecutor.execute(() -> { try { List<Player> players = getPlayerList(userId); cb.ok(players); } catch (Exception e) { cb.fail(e); } }); } // 耗時的數據庫操做 private static List<Player> getPlayerList(int userId) { return new ArrayList<>(); } }
getPlayerList()方法接收兩個參數。第一個參數是用戶ID,第二個參數是個callback,當數據庫操做完成(成功或失敗)時。會通知這個callback。DAO內部使用的多是JDBC或MyBatis等,總之是耗時的操做,由數據庫線程池運行。server
DbOpExecutor的代碼比較簡單。和GameLogicExecutor類似,例如如下所看到的:
import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 數據庫操做線程池. */ public class DbOpExecutor { // todo 依據cpu數等肯定線程數 private static final Executor executor = Executors.newFixedThreadPool(4); /** * 把數據庫操做放進隊列. * @param dbOp */ public static void execute(Runnable dbOp) { executor.execute(dbOp); } }
最後看一下DbOpCallback代碼:
/** * 數據庫操做回調. * @param <T> */ @FunctionalInterface public interface DbOpCallback<T> { /** * 處理數據庫返回結果. * @param result */ void handleResult(T result); /** * 數據庫操做正常結束. * @param result */ default void ok(T result) { // 在遊戲邏輯線程裏處理結果 GameLogicExecutor.execute(() -> { try { handleResult(result); } catch (Exception e) { // todo 處理異常 } }); } /** * 數據庫操做出現異常. * @param e */ default void fail(Exception e) { // todo 處理異常 } }
上面的代碼。在Java8以前用匿名內部類也是可以寫的,僅僅是相比Lambda表達式,更加冗長醜陋而已。另外要注意,上面的代碼僅僅是簡化後的演示樣例代碼,並非真實代碼。
假上面的代碼設想到本身的項目,還須要注意的是異常處理。