worker means "worker". In the WorkerThread pattern, ** worker threads ** go to get jobs one by one and process them. If there is no job, the worker thread waits for a new job. Worker Thread is sometimes called ** Background Thread **.
Consider the following example. A thread in the ClientThread class makes a job request to the Channel class. An instance of the Channel class has five worker threads. The worker thread is waiting for a job request. When a job request arrives, the worker thread receives one job request from Channel and processes it. When the process is finished, the worker thread returns to Channel and waits for the next request.
(See this manual for the entire code)
Main.java
public class Main {
public static void main(String[] args) {
Channel channel = new Channel(5); //Create 5 worker threads
channel.startWorkers(); //Start a worker thread
new ClientThread("Alice", channel).start(); //Start a thread to make a job request
new ClientThread("Bobby", channel).start();
new ClientThread("Chris", channel).start();
}
}
ClientThread.java
public class ClientThread extends Thread {
...
public void run() {
try {
for (int i = 0; true; i++) {
Request request = new Request(getName(), i);
channel.putRequest(request); //Make a job request
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
}
}
}
Channel.java
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[] threadPool;
public Channel(int threads) {
this.requestQueue = new Request[MAX_REQUEST];
this.head = 0;
this.tail = 0;
this.count = 0;
threadPool = new WorkerThread[threads];
for (int i = 0; i < threadPool.length; 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) {
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;
}
}
WorkerThread.java
public class WorkerThread extends Thread {
...
public void run() {
while (true) {
Request request = channel.takeRequest(); //Go get a job
request.execute();
}
}
}
The Channel class has a field called requestQueue for passing work requests. The putRequest method puts a request in this queue, and the takeRequest method retrieves it. Here, the Producer-Consumer pattern is used. The Thread-Per-Message pattern spawned a new thread each time it did a job. However, in the Worker Thread pattern, ** worker threads perform work repeatedly, so there is no need to start a new thread. ** WorkerThread has only an instance of Channel for you to get a job request, and doesn't know anything about the specific job.
Client role The Client role creates a job request as a Request role and passes it to the Channel role. In the sample program, the ClientThread class played this role.
Channel role The Channel role receives the Request role from the Client role and passes it to the Worker role. In the sample program, the Channel class played this role.
Worker role The Worker role receives the Request role from the Channel role and executes the work. After work, go get the next Request role. In the sample program, the WorkerThread class played this role.
Request role The Request role is to represent a job. The Request role holds the information necessary to perform the task. In the sample program, the Request class played this role.
Increased throughput Unlike the Thread-Per-Message pattern, one of the themes of the Worker Thread pattern is ** to reuse threads and recycle them to increase throughput **.
Capacity control The amount of services that can be provided at the same time is called capacity. The number of worker roles can be freely determined. In the sample program, this is the argument threads given to the Channel constructor. The more worker roles you have, the more work you can do in parallel, but preparing more worker roles than the number of jobs requested at the same time is useless and consumes more resources. The number of worker roles does not necessarily have to be set at startup, and can be changed dynamically. The Channel role holds the Request role. If the number of Request roles that can be held by the Channel role is large, the difference in processing speed between the Client role and the Worker role can be filled (buffered). However, a large amount of memory is consumed to hold the Request role. You can see that there is a ** capacity-resource trade-off ** here.
Separation of invocation and execution When making a normal method call, "method invocation" and "method execution" are performed in succession. ** In a normal method call, invocation and execution are inseparable **. However, ** Worker Thread patterns and Thread-Per-Message patterns separate method invocation and execution **. Invocation of a method is called invocation, and execution is called execution. ** Worker Thread pattern and Thread-Per-Message pattern can be said to separate method invocation and execution **. This has the following merits.
--Improved responsiveness Even if it takes a long time to execute, those who have invocation can proceed.
--Execution order control (scheduling) You can prioritize the Request role and control the order in which the Channgel role passes the Request role to the Worker role. This is called request ** scheduling **.
--Cancellable / Repeatable Although invocation was done, execution can realize a function called cancellation.
--Distributed processing It becomes easier to separate the computer to invoke and the computer to execute.
Polymorphic Request role For example, if you create a subclass of the Request class and pass its instance to Channel, WorkerThread will call execute for that instance without any problems. This can be described as utilizing ** polymorphism **. Since all the information necessary to execute the work is described in the Request role, even if a polymorphic Request role is created and the types of work are increased, the Channel role and the Worker role do not need to modify it. Even if the types of work increase, the Worker role still just calls the execute method of the Request role.
Relation Summary of "Design Patterns Learned in Java Language (Multithreaded Edition)" (Part 1) Summary of "Design Patterns Learned in Java Language (Multithreaded Edition)" (Part 2) Summary of "Design Patterns Learned in Java Language (Multithreaded Edition)" (Part 3) Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 4) Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 5) Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 6) Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 7) Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 8) Summary of "Design Patterns Learned in Java Language (Multithreaded Edition)" (Part 9)
Recommended Posts