多线程基础之设计模式Worker-Thread模式

一. Worker-Thread介绍

        我们可以使用 Thread-Per-Message 模式将 "发出工作请求的线程" 与 "执行工作请求的线程" 分开, 来提高程序的响应速度, 但是如果每次发出工作请求时都要创建执行工作的线程就太浪费了性能了, 这里介绍 Worker-Thread 模式, 可以事先启动执行工作的线程, 然后使用 Producer-Consumer 模式将表示工作内容的实例传递给工作线程, 这就是Worker-Thread模式

        Worker 的意思是工人劳动者, 在 Worker-Thread 模式中, 工人线程(WorkerTHread)会逐个取回工作并进行处理, 当所有工作完成后, 工人线程会等待新的工作到来, 该模式也被称为 Background Thread(背景线程模式), 如果从 "保存多个工人下线程的场所" 这一点来看, 这种模式也被称为 Thread Pool 模式

二. 示例程序

类名 说明
Main 测试程序类
ClientThread 表示发出工作请求的线程的类
Request 表示工作请求的类
Channel 接收工作请求并将工作请求交给工人线程的类
WorkerThread 表示工人线程的类

2.1 Main类

        Main 类会创建一个雇佣了五个工人线程的 Channel 的实例, 并将其共享给三个 ClientThread 的实例(Alice, Bobby, Chris)

public class Main {
    public static void main(String[] args) {
        Channel channel = new Channel(5);
        channel.startWorkers();
        new ClientThread("alice", channel).start();
        new ClientThread("bobby", channel).start();
        new ClientThread("chris", channel).start();
    }
}

2.2 ClientThread类

        ClientThread 类的是发送工作请求类, 会创建 Request 实例, 并将该实例传递给 Channel 类的 putRequest() 方法

public class ClientThread extends Thread {
    private final Channel channel;
    private static final Random R = 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(R.nextInt(1000));
            }
        } catch (InterruptedException e) {
            //
        }
    }
}

2.3 Request类

       Request 类是表示工作请求的类

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

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

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

    @Override
    public String toString() {
        return "Request{" + "name='" + name + '\'' + ", num=" + num + '}';
    }
}

2.4 Channel类

        Channel 是负责传递工作请求以及保存工人线程的类, 为了传递请求, 在 Channel 类中定义 RequestQueue 字段, 该字段扮演保存队列的角色, putRequest() 方法用于将请求加入到队列中, takeRequest() 方法用于取出队列中的请求, 这里使用到了 Producer-Consumer 模式, 另外为了实现 putRequest() 方法, 这里还是用到了 Guarded Suspension 模式

        Channel 类中定义一个用于保存工人线程的 threadPool 字段, threadPool 是 WorkerThrea 的数组, Channel 类的构造函数会初始化 threadPool 字段并创建 WorkerThread 的线程实例

public class Channel {
    private static final int MAX_REQUEST = 100;
    private final Request[] requestQueue;
    private int tail;
    private int head;
    private int count;
    private final WorkerThread[] workerThreadPool;

    public Channel(int threadCount) {
        this.requestQueue = new Request[MAX_REQUEST];
        this.head = 0;
        this.tail = 0;
        this.count = 0;
        workerThreadPool = new WorkerThread[threadCount];
        // 初始化工作线程
        for (int i = 0; i < workerThreadPool.length; i++) {
            workerThreadPool[i] = new WorkerThread(" Worker-" + i, this);
        }
    }

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

    public synchronized void putRequest(Request request) {
        while (count >= requestQueue.length) {
            try {
                wait();
            } catch (InterruptedException e) {
                //
            }
        }
        requestQueue[tail] = request;
        tail = (tail + 1) % requestQueue.length;
        count++;
        // 唤醒工作线程
        notifyAll();
    }

    public synchronized Request takeRequest() {
        while (count <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                //
            }
        }
        Request request = requestQueue[head];
        head = (head + 1) % requestQueue.length;
        count--;
        // 唤醒客户端生产
        notifyAll();
        return request;
    }
}

2.5 WorkerThread类        

        WorkerThread 是表示工人线程的类, 工人线程一旦启动后就会一直工作, 会反复执行 "获取一个新的 Request 的实例, 然后调用它的 execute() 方法进行处理

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) {
            Request request = channel.takeRequest();
            request.execute();
        }
    }
}

2.5 运行实例

        从图中可以看出, Worker-0, 1, 2, 3, 4 这五个 WorkerThread 线程正在处理来自于 alice, bobby, chris这三个 ClientThread 的请求, 发送请求的 ClientThread 与处理请求的 WorkerThread 之间没有固定的对应关系, 工人线程不不在意是谁发送的请求, 它只处理接收到的请求              

 Worker-4 executes Request{name='alice', num=0}
 Worker-0 executes Request{name='bobby', num=0}
 Worker-3 executes Request{name='chris', num=0}
 Worker-1 executes Request{name='alice', num=1}
 Worker-0 executes Request{name='alice', num=2}
 Worker-2 executes Request{name='chris', num=1}
 Worker-0 executes Request{name='bobby', num=1}
 Worker-1 executes Request{name='alice', num=3}
 Worker-4 executes Request{name='chris', num=2}
 Worker-3 executes Request{name='bobby', num=2}
 Worker-0 executes Request{name='bobby', num=3}
 Worker-2 executes Request{name='bobby', num=4}
 Worker-0 executes Request{name='alice', num=4}
 Worker-1 executes Request{name='bobby', num=5}
 Worker-3 executes Request{name='chris', num=3}

三. Worker-Thread模式中的登场的角色

3.1 Client(委托类)

        Client 创建表示工作请求的 Request 角色并将该角色传递给 Channel, 在示例程序中, 有 ClientThread 类扮演该角色

3.2 Channel(通信线路)

        Channel 接收来自于 Cilent 的 Request 请求, 并将其传递给 Worker 角色

3.3 Worker(工人)

        Worker 从 Channel 中获取 Request 角色, 并进行工作, 当一项工作完成后, 他会继续去获取另外的 Request

3.4 Request(请求)

        Request 表示工作实例, 保存了进行工作所必须的信息, 字段声明为final, 是线程安全的

3.5 模式类图

多线程基础之设计模式Worker-Thread模式_第1张图片                        

四. java.util.concurrent包和Worker-Thread模式

        java.util.concurrent.ThreadPoolExecutor 类是管理工人线程的类, ThreadPoolExecutor 可以轻松的实现 Worker-Thread 模式, 这里使用 java.util.concurrent.Exectors 类中的静态方法改造上面的示例程序

        此时已经不在需要 WorkerThread 类,  因为已经使用juc包下的线程池替代了, 也不需要 Channel 类了, 因为线程池内部维护着一个 BlockingQueue (阻塞队列)

4.1 通过java.util.concurrent包改造Main类

        Main 类使用 Executors.newFixedThreadPool 方法创建了一个线程池, 该线程池保存了指定数量的线程, 是一个 ExecutorService 对象, 改造后 Main 类如下

public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try {
            new ClientThread("alice", executorService).start();
            new ClientThread("bobby", executorService).start();
            new ClientThread("chris", executorService).start();
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            //
        } finally {
            executorService.shutdown();
        }
    }
}

4.2 通过java.util.concurrent包改造ClientThread类        

public class ClientThread extends Thread {
    private final ExecutorService executorService;
    private static final Random R = new Random();
    public ClientThread(String name, ExecutorService executorService) {
        super(name);
        this.executorService = executorService;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; true; i++) {
                Request request = new Request(getName(), i);
                executorService.execute(request);
                Thread.sleep(R.nextInt(1000));
            }
        } catch (InterruptedException e) {
            //
        }
    }
}

4.3 改造Request类      

        为了能够让 ExecutorService 类使用 Request 类, 下面的程序必须实现 Runnable 接口, 在 run() 方法中做实际的处理

public class Request implements Runnable{
    private final String name;
    private final int num;
    private static final Random R = new Random();

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

    @Override
    public String toString() {
        return "Request{" + "name='" + name + '\'' + ", num=" + num + '}';
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " executes " + this);
        try {
            Thread.sleep(R.nextInt(1000));
        } catch (InterruptedException e) {
            //
        }
    }
}

你可能感兴趣的:(多线程学习基础之设计模式,设计模式,学习,java,后端,java-ee)