【线程】Executor

本文探讨了在生产环境中直接为每个任务创建线程的问题,分析了线程生命周期的开销,并介绍了使用Executor机制来管理和调度线程的优势。通过三个示例程序展示了不同类型的Executor实现及其生命周期管理。

在生产环境中,为每一个任务分配一个线程,尤其是在大量创建线程的时候是由一定缺陷的:

  • 线程生命周期的开销非常高。线程的创建与销毁需要JVM和操作系统提供一些辅助操作,如果请求率非常高,那么为每个请求创建一个新线程将消耗大量的计算资源。

  • 资源消耗高。活跃的线程会消耗大量的内存资源。

  •  稳定性差。线程过多可能抛出OutOfMemoryError异常。


Executor 和 Executors


Executor是提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用Executor来创建线程,而不是使用new Thread显示的创建线程。

Executors可以理解为Executor的工厂类。提供了创建Executor类型的工厂方法。

程序一:为每一个任务创建一个线程


public class AsynExecutor implements Executor{
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }

    public static void main(String[] args) {
        AsynExecutor ae = new AsynExecutor();
        for(int i=0;i<5;i++) {
            ae.execute(new MyRunnable(""+i));
        }
    }
}

public class MyRunnable implements Runnable {

    String name ;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name + " 执行任务");
    }

}


程序二:顺序执行任务


public class SynExecutor implements Executor{
    @Override
    public void execute(Runnable command) {
        command.run(); 
    }

    public static void main(String[] args) {
        SynExecutor se = new SynExecutor();
        for(int i=0;i<5;i++) {
            se.execute(new MyRunnable(""+i));
        }
    }
}


Executor 生命周期

Executor扩展了ExecutorService接口,添加了一些用于管理生命周期的方法与便于任务提交的方法。

ExecutorService的状体分为运行关闭终止。在初始创建时处于运行状态。shutdown方法将执行平缓的的关闭过程,启动一次顺序关闭,执行以前提交的任务,但不接受新任务;shutdownNow方法执行粗暴的关闭过程,试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。


 程序三:Executor的生命周期


public class ExecutorLifeCycle {

    static volatile int i=0;
    public static void main(String[] args) {

        ExecutorService es = Executors.newFixedThreadPool(3);

        es.execute(new Runnable() {
            @Override
            public void run() {
                while(true) {
                        if(i == 2) {
                            es.shutdown();
                            System.out.println("执行关闭的动作");
                            break;
                        }
                }
            }
        });


        for(i=0;i<3;i++) {
            try {
                Thread.sleep(400);

                if(!es.isShutdown()) {
                    System.out.println("加入到es的线程i = "+i);
                    es.execute(new MyRunnable("加入的线程:"+i));
                }

            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    }
}

如果ExecutorService不关闭,程序将不会退出

  

在使用 Java 的 `Executor` 框架进行并发编程时,虽然 `ExecutorService` 抽象了任务的执行细节,使得开发者无需直接操作线程对象,但仍然可以通过一些方式获取正在执行任务的线程 ID。 一种常见的方法是在任务内部通过 `Thread.currentThread().getId()` 获取当前线程的 ID。例如,在实现 `Runnable` 接口的任务中,可以输出当前线程的 ID: ```java public class Task implements Runnable { @Override public void run() { System.out.println("Current thread ID: " + Thread.currentThread().getId()); } } ``` 当将此类任务提交给 `ExecutorService` 执行时,控制台会输出每个任务实际运行所在的线程 ID [^3]。 如果需要更细粒度地跟踪线程信息,或者希望在线程池外部记录线程行为,则可以在创建线程池时提供自定义的 `ThreadFactory` 来设置线程名称或其他属性,尽管这不会改变线程 ID,但它可以帮助识别特定线程的行为 [^2]。 此外,对于需要返回值的任务,可以使用 `Callable` 接口配合 `Future` 对象来获取异步计算的结果,包括但不限于线程 ID [^5]。 ### 示例代码 下面是一个简单的示例,演示了如何在 `ExecutorService` 中使用 `Callable` 接口来获取线程 ID: ```java import java.util.concurrent.*; public class ThreadIdFetcher { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Long> future = executorService.submit((Callable<Long>) () -> { return Thread.currentThread().getId(); }); long threadId = future.get(); // 获取线程ID System.out.println("Thread ID: " + threadId); executorService.shutdown(); } } ``` 在这个例子中,我们向 `ExecutorService` 提交了一个 `Callable` 类型的任务,该任务返回了当前线程的 ID [^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值