Java 中的领导者-跟随者模式:通过动态工作分配提高效率
领导者/跟随者设计模式的意图
为了有效地管理一组工作线程,领导者-跟随者模式允许多个线程轮流共享一组事件源,与每个源一个线程的方法相比,优化了资源利用率并提高了性能。
领导者/跟随者模式的详细解释以及现实世界中的例子
现实世界中的例子
想象一下管理一家繁忙的餐厅,有多个服务员和一个领班。领班充当“领导者”,负责接待客人、管理等候名单和安排座位。客人入座后,领班会回到入口处接待新到的客人。服务员,即“跟随者”,则等待领班分配他们桌位。这种分配是轮流进行的,下一个空闲的服务员会接手下一组客人。这种系统确保领班有效地处理客人流量,而服务员则专注于提供服务,这与领导者和跟随者模式如何在软件系统中管理线程和任务类似。这种方法优化了资源利用率(在本例中是员工),并确保在高峰时段平稳运行,就像它在计算环境中优化线程使用一样。
通俗地说
领导者-跟随者设计模式利用单个领导者线程将工作分配给多个跟随者线程,有效地管理任务委派、线程同步并提高并发编程中的资源效率。
在集群中选择一个服务器作为领导者。领导者负责代表整个集群做出决策,并将决策传播到所有其他服务器。
Java 中领导者-跟随者模式的编程示例
领导者-跟随者模式是一种并发设计模式,其中一个线程(领导者)等待工作到来,进行多路分解、调度和处理工作,从而提高 CPU 缓存亲和性并降低事件调度延迟。领导者完成工作处理后,会将跟随者线程中的一个提升为新的领导者。这种模式对于提高 CPU 缓存亲和性、最大程度地减少锁定开销和降低事件调度延迟非常有用。
在提供的代码中,我们有一个 WorkCenter
类,它管理着一组 Worker
线程。这些工作线程中的一个被指定为领导者,负责接收和处理任务。处理完任务后,领导者会从剩余的工作线程中提升一个新的领导者。
// WorkCenter class
public class WorkCenter {
@Getter
private Worker leader;
private final List<Worker> workers = new CopyOnWriteArrayList<>();
// Method to create workers and set the initial leader
public void createWorkers(int numberOfWorkers, TaskSet taskSet, TaskHandler taskHandler) {
for (var id = 1; id <= numberOfWorkers; id++) {
var worker = new Worker(id, this, taskSet, taskHandler);
workers.add(worker);
}
promoteLeader();
}
// Method to promote a new leader
public void promoteLeader() {
Worker leader = null;
if (!workers.isEmpty()) {
leader = workers.get(0);
}
this.leader = leader;
}
}
在 Worker
类中,每个工作线程都是一个等待处理任务的线程。如果工作线程是领导者,则它会处理该任务,然后提升一个新的领导者。
// Worker class
public class Worker implements Runnable {
private final long id;
private final WorkCenter workCenter;
private final TaskSet taskSet;
private final TaskHandler taskHandler;
@Override
public void run() {
while (!Thread.interrupted()) {
try {
if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) {
synchronized (workCenter) {
if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) {
workCenter.wait();
continue;
}
}
}
final Task task = taskSet.getTask();
synchronized (workCenter) {
workCenter.removeWorker(this);
workCenter.promoteLeader();
workCenter.notifyAll();
}
taskHandler.handleTask(task);
workCenter.addWorker(this);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
在 App
类中,我们创建一个 WorkCenter
,将任务添加到 TaskSet
中,然后启动工作线程。领导者工作线程将开始处理任务,并在完成一个任务后,会提升一个新的领导者。
// App class
public class App {
public static void main(String[] args) throws InterruptedException {
var taskSet = new TaskSet();
var taskHandler = new TaskHandler();
var workCenter = new WorkCenter();
workCenter.createWorkers(4, taskSet, taskHandler);
addTasks(taskSet);
startWorkers(workCenter);
}
private static void addTasks(TaskSet taskSet) throws InterruptedException {
var rand = new SecureRandom();
for (var i = 0; i < 5; i++) {
var time = Math.abs(rand.nextInt(1000));
taskSet.addTask(new Task(time));
}
}
private static void startWorkers(WorkCenter workCenter) throws InterruptedException {
var workers = workCenter.getWorkers();
var exec = Executors.newFixedThreadPool(workers.size());
workers.forEach(exec::submit);
exec.awaitTermination(2, TimeUnit.SECONDS);
exec.shutdownNow();
}
}
这是一个领导者/跟随者模式的基本示例。领导者工作线程处理任务,并在完成一个任务后提升一个新的领导者。然后,新领导者开始处理下一个任务,循环继续。
何时在 Java 中使用领导者/跟随者模式
- 领导者-跟随者模式在需要高效处理单个线程上的多个服务的场景中很有用,可以避免资源浪费,并提高并发编程环境的可扩展性。
- 适用于需要用最少的资源消耗同时处理多个客户端请求的服务器环境。
Java 中领导者-跟随者模式的现实世界应用
- 处理多个传入连接的网络服务器。
- 管理大量输入/输出源的事件驱动应用程序。
领导者-跟随者模式的优点和权衡
优点
- 减少了线程数量和上下文切换,从而提高性能并降低资源利用率。
- 提高了系统的可扩展性和响应能力。
权衡
- 管理领导者和跟随者之间同步的复杂性增加。
- 如果实现不当,可能会导致资源利用不足。
相关的 Java 设计模式
- 半同步/半异步:领导者和跟随者可以看作是一种变体,其中同步方面在领导者(同步处理)和跟随者(异步等待)之间分配。
- 线程池:两种模式都管理着工作线程池,但线程池将任务分配给任何可用线程,而不是使用领导者来分配工作。