多線程設計模式:第四篇 - Thread-Per-Message模式和Worker-Thread模式

一,Thread-Per-Message模式

        Thread-Per-Message模式是說爲每個請求都分配一個線程,由這個線程來執行處理。這裏包含兩個角色,請求的提交線程和請求的執行線程。

        下面的示例代碼中,Host 提交一個請求交給另外一個線程來處理。

/**
 * @author koma <[email protected]>
 * @date 2018-10-18
 */
public class Main {
    public static void main(String[] args) {
        System.out.println("Main BEGIN");
        Host host = new Host();
        host.request(10, 'A');
        host.request(20, 'B');
        host.request(30, 'C');
        System.out.println("Main END");
    }
}

public class Host {
    private final Helper helper = new Helper();

    public void request(final int count, final char c) {
        System.out.println("Request BEGIN");
        new Thread() {
            @Override
            public void run() {
                helper.handle(count, c);
            }
        }.start();
        System.out.println("Request END");
    }
}

public class Helper {
    public void handle(int count, char c) {
        System.out.println("Helper BEGIN");
        for (int i = 0; i < count; i++) {
            slowly();
            System.out.print(c);
        }
        System.out.println();
        System.out.println("Helper END");
    }

    private void slowly() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

        Thread-Per-Message模式可以提高程序的響應性,但是並不能保證操作的順序,而且沒有返回值。一般用於簡單服務器實現,當服務器主線程收到客戶端的請求之後就創建一個新的線程去處理客戶端的請求,而服務器主線程則返回繼續接收客戶端請求。

1,Executor 框架

        Java 中通常是通過 new Thread 或者 new Runnable 實現類來創建線程,在 juc 包中提供了 Executors,ThreadFactory,ExecutorService 另外三種方式來更加方便的創建線程或者線程池等,在以後的代碼中,我們推薦使用這種方式創建線程。

二,Worker-Thread模式

        Worker-Thread模式也被稱爲 Background Thread (背景線程)模式,這種模式不同於 Thread-Per-Message 模式在任務到來時創建線程,而是會先創建一些工作線程等待任務到來,從這個角度來說 Worker-Thread 模式也可被稱爲線程池模式。

        下面的示例程序模擬了Worker-Thread模式的工作過程,代碼如下:

/**
 * @author koma <[email protected]>
 * @date 2018-10-18
 */
public class Main {
    public static void main(String[] args) {
        Channel channel = new Channel(5);
        channel.startWorkers(); //啓動工作線程
        new ClientThread("ClientA", channel).start();
        new ClientThread("ClientB", channel).start();
        new ClientThread("ClientC", channel).start();
    }
}

public class Channel {
    private static final int MAX_REQUESTS = 100; //定義請求隊列最大長度
    private final Queue<Request> queue;
    private final WorkerThread[] threadPool;

    public Channel(int threads) {
        queue = new LinkedList<>();

        threadPool = new WorkerThread[threads];
        for (int i = 0; i < threads; i++) {
            threadPool[i] = new WorkerThread("Worker-"+i, this);
        }
    }

    public void startWorkers() {
        for (int i = 0; i < threadPool.length; i++) {
            threadPool[i].start();
        }
    }

    public synchronized void putRequest(Request request) throws InterruptedException {
        while (queue.size() >= MAX_REQUESTS) {
            wait();
        }
        queue.offer(request);
        notifyAll();
    }

    public synchronized Request takeRequest() throws InterruptedException {
        while (queue.size() <= 0) {
            wait();
        }
        Request request = queue.poll();
        notifyAll();
        return request;
    }
}

public class Request {
    private final String name;
    private final int number;
    private static final Random random = new Random();

    public Request(String name, int number) {
        this.name = name;
        this.number = number;
    }

    public void execute() {
        System.out.println(Thread.currentThread().getName()+" execute "+this);
        try {
            Thread.sleep(random.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return "[ Request from "+name+", No."+number+" ]";
    }
}

public class ClientThread extends Thread {
    private final Channel channel;
    private static final Random random = new Random();

    public ClientThread(String name, Channel channel) {
        super(name);
        this.channel = channel;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; true; i++) {
                Request request = new Request(getName(), i);
                channel.putRequest(request);
                Thread.sleep(random.nextInt(1000));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class WorkerThread extends Thread {
    private final Channel channel;

    public WorkerThread(String name, Channel channel) {
        super(name);
        this.channel = channel;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Request request = channel.takeRequest();
                request.execute();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

        示例中 Channel 類和生產者-消費者模式中一樣,我們也可以使用 Java 中的 BlockingQueue 來實現,從而省去我們自己寫 wait/notify 邏輯的複雜性。

        Worker-Thread模式中應用到了生產者-消費者模式,因爲提交請求的線程和處理請求的線程不是同一個線程。Worker-Thread模式的優點在於它的工作線程是預先啓動好的,這樣節省了線程啓動的時間損耗,提高了程序的性能,而且工作線程是可以重複利用的,節省了程序的資源消耗。

1,ThreadPoolExecutor

        juc包通過 ThreadPoolExecutor 類輕鬆的實現了Worker-Thread模式,通過該類可以對工作線程做非常精細的控制,如下:

  • 指定線程池的大小
  • 指定創建線程的方式:提前創建,按需創建
  • 指定線程創建的工廠類:更加細緻的控制創建出來的線程
  • 指定多長時間後終止不再需要的線程
  • 指定傳遞任務的隊列的排隊方式
  • 指定當隊列飽和時拒絕請求的方式
  • 指定線程執行工作前,執行工作後的處理動作

        通常我們會通過 Executors 類中的靜態方法來創建線程池。利用 Executor 框架改寫後的 WorkerThread 類,以及啓動 Worker 的代碼如下:

public class WorkerThread implements Runnable {
    //代碼不變,僅僅把 WorkerThread 改成實現 Runnable 接口
    //爲了傳遞到 Executors 類中
}

public class Channel {
    //創建工作線程池
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    //其它代碼不變,記得將原來的 threadPool 代碼註釋掉

    //從線程池中啓動工作線程
    public void startWorkers() {
        executorService.execute(new WorkerThread(this));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章