1. 程式人生 > 實用技巧 >簡單的Tomcat實現--1.6多執行緒

簡單的Tomcat實現--1.6多執行緒

多執行緒處理

  • 現階段我們的bootstrap.java是單執行緒序列的,當有多個請求同時到達時,就像上一次使用多執行緒模擬同時訪問耗時任務時,伺服器也只能序列的去訪問。

  • 首先建立一個執行緒池作為工具。

  • package util;
    
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author :xiaosong
     * @description:TODO
     * @date :2020/8/4 15:04
     */
    public class ThreadUtil {
        private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                20, 100, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(10)
        );
    	// run方法接受runnable並利用執行緒池進行處理
        public static void run(Runnable r){
            threadPool.execute(r);
        }
    }
    
  • 這裡有必要詳細的說明一下ThreadPoolExetor的構造方法引數

    • 第一個引數是核心執行緒數為20
    • 第二個引數表示這個執行緒池最多會出現100個執行緒
    • 第三個引數和第四個引數合起來表示當新增的執行緒,即大於20的部分如果空閒時間超過60s就會被回收
    • 第五個引數是快取區,使用LinkedBlockQueue來儲存那些短時間內激增的請求,這些請求不會立刻讓執行緒池增加執行緒,而是超過了這個緩衝區之後,才會增加執行緒。
  • 當有請求過來時,生成Runnable物件,然後將這個物件對給執行緒池處理即可。

  • import cn.hutool.core.io.FileUtil;
    import cn.hutool.core.thread.ThreadUtil;
    import cn.hutool.core.util.ArrayUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.log.LogFactory;
    import cn.hutool.system.SystemUtil;
    import http.Request;
    import http.Response;
    import util.Constant;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * @author :xiaosong
     * @description:專案的啟動類
     * @date :2020/7/28 20:41
     */
    public class Bootstrap {
        /**
         * 定義伺服器的埠號
         */
        final static int PORT = 10086;
    
        public static void main(String[] args) {
            try {
                logJvm();
                // 在port埠上新建serverSocket
                ServerSocket serverSocket = new ServerSocket(PORT);
                // 外部使用一個while迴圈,當處理完一個Socket的連結請求之後,再處理下一個連結請求
                while (true) {
                    Socket socket = serverSocket.accept();
                    // 從socket中接收瀏覽器的請求
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // 獲取輸入流,這個輸入流表示的是收到一個瀏覽器客戶端的請求
                                Request request = new Request(socket);
    
                                System.out.println("瀏覽器的輸入資訊: \r\n" + request.getRequestString());
                                Response response = new Response();
                                // 先將html資訊寫入到response的Writer的StringWriter中
                                String uri;
                                uri = request.getUri();
                                if (uri == null) {
                                    return;
                                }
                                if ("/".equals(uri)) {
                                    String html = "Hello JerryMice";
                                    response.getWriter().println(html);
                                } else {
                                    // removePrefix()方法可以去掉字串指定的字首
                                    String fileName = StrUtil.removePrefix(uri, "/");
                                    File file = FileUtil.file(Constant.rootFolder, fileName);
                                    if (file.exists()) {
                                        //如果檔案存在,那就去試圖訪問
                                        String fileContent = FileUtil.readUtf8String(file);
                                        // 寫入到response中
                                        response.getWriter().println(fileContent);
                                        // 判斷是否是模擬的耗時任務
                                        if ("timeConsume.html".equals(fileName)) {
                                            ThreadUtil.sleep(1000);
                                        }
                                    } else {
                                        System.out.println("File not found!");
                                    }
                                }
                                System.out.println(uri);
                                // 開啟輸出流,準備給客戶端輸出資訊
                                handle200(socket, response);
                            } catch (IOException e) {
                                LogFactory.get().error(e);
                            }
                        }
                    };
                    util.ThreadUtil.run(runnable);
                }
            } catch (IOException e) {
                LogFactory.get().error(e);
            }
        }
    
        private static void logJvm() {
            // 建立一個Map用於儲存各種資訊
            Map<String, String> infoMap = new LinkedHashMap<>();
            infoMap.put("Server version", "JerryMice 1.0.0");
            infoMap.put("Server build", "2020-08-03");
            infoMap.put("OS:\t", SystemUtil.get("os.name"));
            infoMap.put("OS version", SystemUtil.get("os.version"));
            infoMap.put("Architecture", SystemUtil.get("os.arch"));
            infoMap.put("Java Home", SystemUtil.get("java.home"));
            infoMap.put("JSM Version", SystemUtil.get("java.runtime.version"));
            infoMap.put("JVM Vendor", SystemUtil.get("java.vm.specification.vendor"));
            Set<String> keys = infoMap.keySet();
            for (String key : keys) {
                // 呼叫hutool的LogFactory工廠函式獲取logger,logger會自動根據log4j.properties來對Log4j的Logger進行配置
                LogFactory.get().info(key + ":\t\t" + infoMap.get(key));
            }
        }
    
        /**
         * @param socket:
         * @param response:Response物件,伺服器對瀏覽器請求的響應,可以通過response的getBody()獲取儲存在其中的html文字
         * @throws IOException
         */
        private static void handle200(Socket socket, Response response) throws IOException {
            // 獲取型別
            String contentType = response.getContentType();
            String headText = Constant.responseHead200;
            headText = StrUtil.format(headText, contentType);
            byte[] head = headText.getBytes();
            // 獲取response中的html文字,這個html文字是通過writer寫到stringWriter字元流上的
            byte[] body = response.getBody();
            byte[] responseBytes = new byte[head.length + body.length];
            ArrayUtil.copy(head, 0, responseBytes, 0, head.length);
            ArrayUtil.copy(body, 0, responseBytes, head.length, body.length);
    
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(responseBytes);
            socket.close();
        }
    }
    
  • 單元測試

  • 3個耗時任務請求在1s31ms內就被全部響應完畢了,提現了多執行緒並行。