java中用Executor代替Thread的四大理由

在这里插入图片描述

在组建java程序时候关于任务执行方面第一个要考虑的就是任务边界的划分,即把执行的程序分成合理的任务,理想的情况是这些任务的状态,结果不会影响到其它任务。

以java最擅长的服务器端开发为例子,服务器端要做到两点:高并发(good throughput)、低延迟(good responsiveness)。高并发是服务提供者的需要,我们想让我们的程序承载更多的用户来分担服务器开销;低延迟是用户的需求,用户想尽快的看到服务器对请求的响应结果。同样服务器的任务划分也是比较清晰和明确的:我们可以把用户的一个请求,作为一个任务。在高并发、低延迟的要求下,我们来设计下服务器的并发框架。
首先能想到的是单线程模式,程序如下:

public class SingleThreadWebServer{
    public static void main(String[] args) throws Exception{
        ServerSocket server = new ServerSocket(8900);

        while(true){
            Socket socket = server.accept(); 
            handlerRequest(socket);
        }
    }
}

这个程序中,每个客户端请求和其处理都在一个程序中执行,在handlerRequest方法比较耗时的情况下,用户将感觉到卡顿,没能做到低延迟;同时因为同一时间只能有一个客户端请求被处理,服务器也没能做到高并发。在真实的生产环境中,除了GUI程序,基本上不会用到这种模型框架。

因为handlerRequest方法可能造成延迟,我们改进成程序如下:

public class ThreadPerTaskWebServer{
    public static void main(String[] args){
        ServerSocket serverSocket = new ServerSocket();
        while(true){
            Socket socket = serverSocket.accpet();
            new Thread(){
                @Override
                public void run(){
                    handlerReqeust(socket);
                }
            }.start();
        }
    }
}

此模式下将任务接受和任务执行分别放在不同的线程中来执行,main方法所在的线程主要负责接受客户端请求,具体的任务执行放在单独的一个新的线程中来执行。此模式可以很好的满足服务器高并发,低延迟的设计需求。但还是存在三个缺点:
1、线程生命周期成本太大(thread lifecycle overhead):此模式中,每个请求都要创建一个新的处理线程,当任务处理完毕后,此线程还要被销毁。但是线程的创建和销毁并不是没有成本的。线程的创建需要向jvm申请一些资源,这就给处理客户端请求带来了一些延迟。如果需要此线程处理的任务都是一些短时间的任务,那么线程的创建时间将会称为主要的延迟。
2、资源的消耗严重(Resource consumption):线程是需要消耗资源的,比如内存。当线程的数量超过了处理器的核数,多余的线程将阻塞,这将造成资源的浪费同时给gc造成极大的压力,同时线程之间竞争CPU也会造成一些性能上的牺牲。
3、影响程序稳定性(stability):程序能创建多少线程是有限制的,如果没有限制的创建过多的线程,很可能会得到OutOfMemoryError。

造成上述问题的根本原因是系统没有对程序中可创建的线程数量做限制,对上述程序改进如下:

public class TaskExecutionWebServer{
    private static Executor executor = new Exectutors.newFixedThreadPool(100); //1
    public static void main(String[] args){
        ServerSocket serverSocket = new ServerSocket(9090);
        while(true){
            Socket socket = serverSocket.accpet();
            Runnable task = new Runnable(){
                public void run(){
                    handlerRequest(socket);
                }
            }
            executor.execute(task);
        }
    }
}

Executor 是一个接口,其只有一个方法execute。execute方法用来执行Runnable实例的,我们只需要把我们的handlerRequest方法的方法封装在一个Runnable实例中,然后把这个任务提交给Executor,具体的任务运行管理,交给Executor即可。同时我们在//1处可以看到我们最多可以创建
的线程数量被限定为100个。这就没有了ThreadPerTaskWebServer中的无限制创建线程的问题了。同时把任务的提交和任务的执行解耦还给我们带来了灵活的执行任务策略。我们可以很容易的把执行策略编程单线程模式,只需要把 //1处 exectuor变成一个OneThreadExecutor实例:

public class OneThreadExecutor implements Executor {
    public void execute(Runnable task){
        task.run();
  }
}

我们还可以把执行策略变成我们之前的一个任务一个线程的模式,只需要把 //1处 executor变成ThreadPerTaskExecutor实例:

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

使用Executor接口来执行任务,可以让我们在获得了高并发,低延迟的前提下,又有了很大的灵活性,很方便的更改执行策略。但是考虑一个程序完整的生命周期,不应该只有执行状态,还应该有关闭状态。当程序要关闭的时候,Executor应该能对其做出相应的响应,所以一个Executor也应该有自己的生命周期。为了描述Executor的生命周期,我们引进了ExecutorService接口,其继承了Executor接口。一个ExecutorService实例有三种状态:running、shutting down、terminated。ExecutorService实例在刚刚创建的时候就处在running状态;当调用shutdown方法的时候,ExecutorService实例会把状态置为shutting down,同时把正在执行的任务继续执行,当通过execute方法向其中添加新的任务时,ExecutorService实例可以忽略新添加的任务,或者抛出一个异常,具体取决于策略的选取,这在以后再说。实例中没有正在执行的任务的时候,实例就进入terminated状态。一个具有生命周期管理的WebServer服务器类如下:

public class LifeCycleWebServer{
    private final ExecutorService exec = ....;

    public static void main(String[] args) throws Exception{
        ServerSocket socket = new ServerSocket(9090);
        while(!exec.isShutdown()){
            Socket socket = socket.accpet();
            exec.execute(new Runnable(){
                    public void run(){
                        if(isShutdownRequest(socket)){
                            exec.shutdown();
                        }

                        handleRequest(socket);
                    }
                };)
        }
    }
}

在LifeCycleWebServer类中,我们可以通过一个特殊的客户端请求来让ExecutorService进入关闭状态。这样ExecutorService实例也有了生命周期,更能适合真实的开发需求。

通过以上比较可知,用Executor框架执行任务的好处如下:
1、能够实现高并发和低延迟。
2、因为Executor的实现类可以很好的管理其线程的创建数量,可能任务提交给Executor的时候,已经有可以执行此任务的线程了,这样减少了
线程创建带来的延迟。因为Executor实例限制了,创建线程的数量,减少了线程之间对CPU的竞争,也在一定程度上提高了系统性能。对线程创建数量的限制,保证了系统的稳定性。
3、更加灵活的执行策略,因为Executor实现了任务的提交和执行的解耦,可以让我们灵活的更换任务的执行策略。
4、继承Executor接口的ExecutorService增加了生命周期的管理,更加贴近真实的生产开发。

综上所述,在java中执行任务首要考虑的是Executor,而不是Thread.
,可以让我们灵活的更换任务的执行策略。
4、继承Executor接口的ExecutorService增加了生命周期的管理,更加贴近真实的生产开发。

综上所述,在java中执行任务首要考虑的是Executor,而不是Thread.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来给您讲解一下Java中的Executor框架。 在Java中,Executor框架是一种用于管理线程的高级工具。该框架允许开发者使用高级的API来管理线程,而无需直接使用底层线程管理机制。 Executor框架中的主要接口是ExecutorExecutorService。Executor接口定义了一个用于执行任务的方法,而ExecutorService则是一个扩展了Executor接口的接口,它提供了一系列管理任务和线程的方法,如提交任务、关闭线程池等。 在Executor框架中,任务被封装为实现了Runnable接口或Callable接口的对象。其中,Runnable接口表示一个没有返回值的任务,而Callable接口则表示一个可以返回结果的任务。这些任务被提交给线程池执行,线程池中的线程会从任务队列中获取任务并执行。 Executor框架提供了多种不同类型的线程池,如FixedThreadPool、CachedThreadPool、SingleThreadPool等。其中,FixedThreadPool是一个固定大小的线程池,CachedThreadPool是一个可以自动扩容的线程池,而SingleThreadPool则是一个只有一个线程的线程池。 除了线程池之外,Executor框架还提供了一些其他的工具类,如ScheduledExecutorService用于定时执行任务、CompletionService用于管理异步任务等。 总的来说,Executor框架是一个非常实用的线程管理工具,它简化了线程管理的复杂性,并提供了多种线程池和工具类,帮助开发者更加方便地管理线程。 ### 回答2: Executor框架是Java提供的一个用于管理和调度线程的高级框架。它在java.util.concurrent包下,提供了一组接口和类来处理线程的创建、启动、执行和调度。 Executor框架主要包含了三个核心组件:ExecutorExecutorService和ThreadPoolExecutor。 首先,Executor是一个接口,它定义了一个简单的方法execute(Runnable command),用于执行传入的任务。它隐藏了线程的创建和启动过程,只需要将任务交给Executor,它会在后台自动创建线程并执行任务。 其次,ExecutorService是一个接口,它继承自Executor接口,并增加了一些管理和控制线程的方法。它提供了submit(Callable task)方法,用于提交有返回值的任务,并返回一个Future对象,可以通过该对象获取任务的执行结果。ExecutorService还提供了shutdown()和shutdownNow()方法,用于优雅地停止线程池的运行。 最后,ThreadPoolExecutorExecutorService接口的一个实现类。它实现了一个基于线程池的ExecutorService,可以根据任务的数量和需要的资源动态地创建、回收和管理线程。ThreadPoolExecutor通过内部的任务队列和线程池来实现任务的调度和执行。可以通过调整线程池的参数来控制并发执行的线程数量和线程的优先级。 Executor框架的使用可以提供多线程编程的简化和优化。它可以将任务的提交和执行进行分离,将任务的执行交给线程池管理,从而充分复用线程资源,避免线程创建和销毁的开销。同时,它还提供了任务的调度和控制功能,可以根据需要在多个线程之间进行任务的切换和调度,提高系统的运行效率和资源利用率。 总结来说,在Java中,Executor框架是一个强大的并发编程工具,通过它我们可以方便地管理和控制线程的执行,提高程序的性能和响应速度。 ### 回答3: Executor框架是Java中用于管理和调度线程的一个重要框架。它提供了一种简单且高效的方式来执行异步任务,并且能够更好地管理线程池资源。 Executor框架提供了一个Executor接口,该接口定义了一个线程池的基本执行操作。常见的实现类有ThreadPoolExecutor和ScheduledThreadPoolExecutor,它们都是Executor接口的具体实现。 通过Executor框架,我们可以使用线程池执行任务,从而避免了为每个任务创建新线程带来的开销。我们只需要将任务提交给线程池即可,线程池会自动进行线程的创建和管理,实现任务的异步执行。 Executor框架还提供了一些功能丰富的方法,如submit()方法和invokeAll()方法。submit()方法用于提交一个任务并返回一个表示该任务结果的Future对象,我们可以利用Future对象来获取任务的执行结果或取消任务的执行。invokeAll()方法用于同时提交多个任务,并等待所有任务完成。 除了常规的线程池功能外,Executor框架还引入了一些用于定时任务调度的类,例如ScheduledThreadPoolExecutor和ScheduledExecutorService。这些类可以实现任务的定时执行、周期执行以及延迟执行等功能。 总之,Executor框架是Java中管理和调度线程的重要工具。它提供了一种高效的线程池资源管理方式,并且提供了一些方便的方法来操作和管理任务的执行。在实际开发中,合理地使用Executor框架可以提高程序的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值