【并发编程】实现自己的 executor

本文详细介绍了Java中Executor接口及其实现原理,包括直接执行和每个任务新线程的简单实现及其缺点。重点讨论了线程池结合任务队列的实现方式,以SimpleThreadPoolExecutor为例,展示了如何维护线程池和任务队列来提高效率并避免资源浪费。文章最后指出,通常情况下我们可直接使用Java提供的ThreadPoolExecutor等预定义线程池实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jdk 自带的 executors 的实现都是基于线程池和任务队列的

不过,接口规范并没有对 executors 的实现细节有着严格限制

本小节从代码的角度说明为什么 jdk 要采取 “线程池 + 任务队列” 的方式实现 executors

以 Executor 的接口为例,这个接口只定义了一个 execute 方法:

public interface Executor {
    void execute(Runnable command);
}

在最简单的情况下,执行程序可以在调用者的线程中立即运行已提交的任务:

class DirectExecutor implements Executor {
    public void execute(Runnable r) { 
        r.run(); 
    } 
}

在这种情况下,如果向 Executor 中提交一个任务,会立即进行执行。

其缺点在于,任务的执行不是异步的,某个线程向 Executor 中提交一个任务之后,必须等到这个线程执行完成才能继续往下执行。

更常见的是,任务是在某个不是调用者线程的线程中执行的。

以下执行程序将为每个任务生成一个新线程:

class ThreadPerTaskExecutor implements Executor { 
    public void execute(Runnable r) { 
        new Thread(r).start(); 
    } 
}

在这种情况下,调用者线程提交完任务之后,由于是启动一个新的线程来完成这个任务。因此调用者线程可以立即去执行接下来的代码。

不过这种实现也存在一些问题,如果同时提交的大量的任务,必定会创建大量的线程,线程的上下文切换、创建与销毁,都是很浪费资源的,因此这种设计方式可能会超出计算机的处理能力。

改进的方式,executor 中维护了一定数量的线程,而提交的任务到放到一个任务队列中。

public class SimpleThreadPoolExecutor implements Executor {
 	// 任务队列
    private BlockingQueue<Runnable> taskQueue = null;
    // 线程池
    private List<WorkerThread> threads = new ArrayList<WorkerThread>();
    private boolean isStopped = false;
 
    public SimpleThreadPoolExecutor(int noOfThreads, int maxNoOfTasks ) {
        taskQueue = new LinkedBlockingQueue<Runnable>();
        for (int i =0; i<noOfThreads; i++) {
            threads.add(new WorkerThread(taskQueue));
        }
        for (WorkerThread thread : threads) {
            thread.start();
        }
    }
 
    public synchronized void execute(Runnable task) {
        if(this.isStopped ) {
            throw new 
                IllegalStateException("SimpleThreadPoolExecutor is stopped" );
        }
        this.taskQueue.add(task);
    }
 
    public synchronized void stop() {
        this.isStopped = true;
        // 循环中断每一个线程
        for (WorkerThread thread : threads) {
            thread.toStop();
        }
    }
 
}
 
class WorkerThread extends Thread {
 
    private BlockingQueue<Runnable> taskQueue = null;
    private boolean  isStopped = false;
 
    public WorkerThread(BlockingQueue<Runnable> queue) {
        taskQueue = queue;
    }
 
    public void run() {
        // 因为需要不断从的任务列出中取出 task 执行
        // 因此需要放在一个循环中,否则线程对象执行完一个任务就会立刻结束
        while (!isStopped()) {
            try {
                Runnable runnable = taskQueue.take();
                runnable.run();
            } catch(Exception e ) {
                // 写日志或者报告异常
                // 但保持线程池运行
            }
        }
    }
 
    public synchronized void toStop() {
        isStopped = true;
        // 如果线程正在任务队列中获取任务,或者没有任务被阻塞,需要响应这个中断
        this.interrupt(); 
    }
 
    public synchronized boolean isStopped() {
        return isStopped ;
    }
}

线程池的实现由两部分组成。类 SimpleThreadPoolExecutor 是线程池的公开接口,而类 WorkerThread 用来实现执行任务的子线程。

为了执行一个任务,方法 SimpleThreadPoolExecutor.execute(Runnable r) 用 Runnable 的实现作为调用参数。在内部,Runnable 对象被放入阻塞队列 (Blocking Queue),等待着被子线程取出队列。

一个空闲的 WorkerThread 线程会把 Runnable 对象从队列中取出并执行。你可以在 WorkerThread .run() 方法里看到这些代码。执行完毕后,WorkerThread 进入循环并且尝试从队列中再取出一个任务,直到线程终止。

调用 SimpleThreadPoolExecutor.stop() 方法可以停止 SimpleThreadPoolExecutor。在内部,调用 stop 先会标记 isStopped 成员变量为 true。然后,线程池的每一个子线程都调用 WorkerThread .toStop() 方法停止运行。注意,如果线程池的 execute() 在 stop() 之后调用,execute() 方法会抛出 IllegalStateException 异常。

子线程会在完成当前执行的任务后停止。注意 WorkerThread.stop() 方法中调用了 this.interrupt()。它确保阻塞在 taskQueue.take() 里的处理等待状态的调用的线程能够跳出等待状态。例如本例中,使用的 LinkedBlockingQueue 的 take 方法实现如下:

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
	// 内部通过死循环尝试获取锁,每次循环都判断线程是否中断了,如果中断抛出异常
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            // 如果任务数量为0,当前线程进入等待状态,等待signal信号
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

通常情况下,我们不需要自己实现Executor,java.util.concurrent包中,已经提供了默认的实现,如:ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。

并且提供了一个工具类 Executors,用于创建其实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值