java并行编程:Executor、Executors、ExecutorService版权声明

原创
小哥 3年前 (2022-10-28) 阅读数 177 #JAVA
文章标签 javajava教程

Executors
在Java 5然后,并发编程引入了一系列新的启动、调度和管理线程。API。Executor框架便是Java 5介绍了其内部使用的线程池机制,这是在。java.util.cocurrent 在该包下,框架控制线程的启动、执行和关闭,从而简化了并发编程。因此,在Java 5在那之后,Executor来启动线程而不是使用。Thread的start除了更容易管理和更高效(使用线程池来节省开销)之外,还有一个关键点:它有助于避免this逃逸问题-如果我们在构造函数中启动一个线程,因为另一个任务可能在构造函数结束之前开始执行,我们可能会访问初始化对象的一半。Executor在构造函数中。Eexecutor作为一个灵活而强大的异步执行框架,它支持多种不同类型的任务执行策略,提供了一种基于生产者的分离任务提交和执行流程的标准方法。-使用者模式下,提交任务的线程相当于生产者,执行任务的线程相当于使用者,并使用它。Runnable为了表示任务,Executor该实施还提供生命周期支持,以及统计信息收集、应用程序管理机制和性能监测机制。
一、Executor的UML图:(几个常用的接口和子类)

Executor该框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

二、Executor和ExecutorService
Executor:定义接收的接口。Runnable对象的方法。executor,其方法签名为executor(Runnable command),该方法接收一个Runable实例,该实例用于执行任务,该任务实现Runnable接口的类别,通常,Runnable在新线程中打开任务的用法是:new Thread(new RunnableTask())).start(),但在Executor在中,您可以使用Executor不是在屏幕上创建线程:executor.execute(new RunnableTask()); // 异步执行

ExecutorService:是一个比率。Executor使用更广泛的子类接口,提供了一种管理生命周期的方法,返回。 Future 对象,并且可以跟踪一个或多个异步任务执行返回。Future方法;您可以调用ExecutorService的shutdown()方法以平滑关闭 ExecutorService在调用该方法之后,将导致ExecutorService停止接受任何新任务,并等待提交的任务完成(已提交的任务将分为两类:已执行任务和尚未执行任务。),将在所有提交的任务完成后关闭。ExecutorService。因此,我们通常使用该接口来实现和管理多线程。

通过 ExecutorService.submit() 方法返回 Future 对象,您可以调用isDone()方法查询Future已经完成了。当任务完成时,它有一个您可以调用的结果。get()方法以获得结果。你也不能用它。isDone()直接调用支票。get()得到结果,在这种情况下,get()它将一直阻塞,直到结果就绪,任务的执行也可以取消。Future 提供了 cancel() 方法用于取消执行。 pending 来话任务。ExecutorService 部分代码如下:

public interface ExecutorService extends Executor {
void shutdown();

Future submit(Callable task); Future submit(Runnable task, T result); List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException; } 三、Executors类: 主要用于提供线程池相关的操作。 Executors类提供了一系列用于创建线程池的工厂方法,并实现了返回的线程池。ExecutorService接口。 1、public static ExecutorService newFiexedThreadPool(int Threads) 为固定数量的线程创建一个线程池。 2、public static ExecutorService newCachedThreadPool():创建可缓存的线程池,调用。execute 如果先前构造的线程可用,则将重用该线程。如果没有可用的线程,则会创建一个新线程并将其添加到池中。终止并删除已有的 60 秒是未使用的线程。 3、public static ExecutorService newSingleThreadExecutor():创建单线程Executor。 4、public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建支持定时和定期任务执行的线程池,在大多数情况下可以将其用作替代方案。Timer类。 newCachedThreadPool() -缓存池,首先检查池中以前建立的线程(如果有),然后 reuse.如果不是,则创建一个新线程以加入池。 -缓存池通常用于执行生存期较短的异步任务。 因此,在一些面向连接的daemon型SERVER不是很多。但对于短暂的异步任务来说,情况就是如此。Executor的首选。 -能reuse的线程,必须是timeout IDLE池中的线程,默认为。     timeout是60s,超过这个IDLE在很长一段时间内,线程实例将被终止并移出池。 注,请输入CachedThreadPool线程不必担心它的结尾,更多的是。TIMEOUT如果它不活动,它将被自动终止。 newFixedThreadPool(int) -newFixedThreadPool与cacheThreadPool几乎,也可以reuse使用它,但您不能在任何时候构建新的线程。 -它的独特性:在任何时间点,最多只能存在固定数量的活动线程。此时,如果要建立新线程,则只能将其放入另一个队列中等待,直到当前线程中的线程终止并直接移出池。 -和cacheThreadPool不同,FixedThreadPool没有IDLE机制(也可能有,但因为文件没有提到,所以肯定很长,类似于靠上级TCP或UDP IDLE机械装置等),所以FixedThreadPool它们中的大多数是针对一些非常稳定和固定的常规并发线程的,主要用于服务器。 -从该方法的源代码来看,cache池和fixed 池调用相同的基础 池,但参数不同。: fixed池线程的数量是固定的,0秒IDLE(无IDLE) cache支持的池线程数0-Integer.MAX\_VALUE(显然完全不考虑主机的资源可负担性),60秒IDLE newScheduledThreadPool(int) -调度线程池 -此池中的线可以按下schedule依次delay执行,或定期执行 SingleThreadExecutor() -单线程,任何时间池中只能有一个线程 -用的是和cache池和fixed共享相同的基础池,但线程数是1-1,0秒IDLE(无IDLE) 四、Executor VS  ExecutorService VS Executors 如上所述,这三个都是 Executor 框架的一部分。Java 为了更有效地使用它们,开发人员有必要学习和理解它们。 Java 提供了不同类型的线程池。总结三者之间的区别,以便大家更好地理解: Executor 和 ExecutorService 这两个接口之间的主要区别是:ExecutorService 接口继承 Executor 接口,是 Executor 的子接口 Executor 和 ExecutorService 第二个区别是:Executor 该接口定义 execute()方法用于接收Runnable对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。 Executor 和 ExecutorService 接口之间的第三个区别是 Executor 中的 execute() 方法不返回任何结果,并且 ExecutorService 中的 submit()方法可以通过 Future 该对象返回操作的结果。 Executor 和 ExecutorService 该界面的第四个不同之处在于除了允许客户端提交任务之外,ExecutorService 还提供了控制线程池的方法。例如:呼叫 shutDown() 方法终止线程池。可以通过 《Java Concurrency in Practice》 了解更多有关关闭线程池以及如何处理线程池的信息。 pending 这项任务的知识。 Executors 类提供工厂方法来创建不同类型的线程池。例如: newSingleThreadExecutor() 创建只有一个线程的线程池,newFixedThreadPool(int numOfThreads)为了创建具有固定数量的线程的线程池,newCachedThreadPool()您可以根据需要创建新线程,但如果现有线程处于空闲状态,则可以重新使用它们。 这里有一个Executor执行Callable该任务的示例代码: import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class CallableDemo{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List> resultList = new ArrayList>(); //创建10任务和执行 for (int i = 0; i < 10; i++){ //使用ExecutorService执行Callable任务类型并将结果保存在future变量中 Future future = executorService.submit(new TaskWithResult(i)); //存储任务执行的结果。List中 resultList.add(future); } //遍历任务的结果 for (Future fs : resultList){ try{ while(!fs.isDone);//Future如果没有完成则返回,循环等待。Future返回完成 System.out.println(fs.get());     //打印每个线程(任务)执行的结果 }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //开始顺序关闭,执行以前提交的任务,但不接受新任务 executorService.shutdown(); } } } } class TaskWithResult implements Callable{ private int id; public TaskWithResult(int id){ this.id = id; } /** * 一旦任务被传递,任务的具体过程。ExecutorService的submit方法, * 该方法在线程上自动执行。 */ public String call() throws Exception { System.out.println("call()方法被自动调用!    " + Thread.currentThread().getName()); //返回结果将为Future的get方法得到 return "call()自动调用该方法,任务返回的结果为:" + id + "    " + Thread.currentThread().getName(); } } 5.自定义线程池 自定义线程池,可以使用。ThreadPoolExecutor创建类时,它有几种创建线程池的构造方法,用这个类很容易实现自定义线程池,下面先粘贴示例程序: import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolTest{ public static void main(String[] args){ //创建等待队列 BlockingQueue bqueue = new ArrayBlockingQueue(20); //使用池中保存的线程数创建线程池。3,则允许的最大线程数为5 ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue); //创建七项任务 Runnable t1 = new MyThread(); Runnable t2 = new MyThread(); Runnable t3 = new MyThread(); Runnable t4 = new MyThread(); Runnable t5 = new MyThread(); Runnable t6 = new MyThread(); Runnable t7 = new MyThread(); //每个任务都在一个线程上执行。 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); pool.execute(t7); //关闭线程池 pool.shutdown(); } } class MyThread implements Runnable{ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "被处决..。"); try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } 行动结果如下: 从结果可以看出,在线程池中的三个线程上执行了七个任务。以下是对使用的简要说明ThreadPoolExecuror类的构造方法中每个参数的含义。 public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue) corePoolSize:线程池中保存的核心线程数,包括空闲线程数。 maximumPoolSize:池中允许的最大线程数。 keepAliveTime:线程池中空闲线程的最大持续时间。 unit:持续时间单位。 workQueue:仅在任务执行前保存任务队列。execute已提交方法Runnable任务。 根据ThreadPoolExecutor源代码前面有一大段评论,当我们试图通过时可以看到这一点。excute方法将是一个Runnable将任务添加到线程池时,将按以下顺序处理它们: 1如果线程池中的线程数较少,corePoolSize,即使线程池中有空闲线程,也会创建一个新线程来执行新添加的任务; 2如果线程池中的线程数大于或等于corePoolSize,但缓冲队列。workQueue如果不是,则放置新添加的任务。workQueue中,按照FIFO轮流等待执行的原则(线程池中的线程空闲后,缓冲区队列中的任务依次交给空闲的线程执行); 3如果线程池中的线程数大于或等于corePoolSize并缓冲该队列。workQueue已满,但线程池中的线程数较少maximumPoolSize创建一个新的线程来处理添加的任务; 4如果线程池中的线程数相等maximumPoolSize,有4此构造方法调用包含5参数的构造方法,最后的构造方法是。RejectedExecutionHandler类型,该类型在处理线程溢出时具有。4这样,我就不在这里详述了。为了理解,我可以阅读源代码。 总而言之,也就是当有新的任务需要处理时,首先看看线程池中的线程数量是否更多corePoolSize,请看缓冲队列。workQueue是否已满,并最终查看线程池中的线程数量是否更多maximumPoolSize。 此外,当线程池中的线程数量较多时corePoolSize如果其中有一个线程已经空闲了更长时间keepAliveTime,则将其从线程池中移除,以便可以动态调整线程池中的线程数量。 让我们粗略地看一看。Executors的源码,newCachedThreadPool的不带RejectedExecutionHandler参数(即,第五个参数,线程数超过maximumPoolSize指定的处理方法)构造如下: public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX\_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 它将corePoolSize设定为0,而将maximumPoolSize设定为了Integer线程空闲的最大值超过60秒,将从线程池中删除。由于核心线程的数量是0,所以每次添加任务时,首先从线程池中找到一个空闲线程,如果没有,则创建一个线程(SynchronousQueue决定执行新任务并将线程添加到线程池,允许的最大线程数。Integer最大值,所以理论上这个线程池可以扩展。 再来看newFixedThreadPool的不带RejectedExecutionHandler该参数的构造方法如下: public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } 它将corePoolSize和maximumPoolSize已设置为nThreads从而实现不动态扩展的固定大小的线程池,此外,keepAliveTime设定为了0也就是说,只要线程处于空闲状态,它就会从线程池中移除并释放LinkedBlockingQueue以下内容将会这样说。 以下是一些排队策略: 1,直接提交。采用缓冲队列 SynchronousQueue,它将任务直接分配给线程,而不保留它们。如果没有线程可用于立即运行任务(即线程池中的所有线程都在工作),则尝试将任务添加到缓冲区队列将失败,因此将构造一个新线程来处理新添加的任务并将其添加到线程池。直接提交通常需要无约束 maximumPoolSizes(Integer.MAX\_VALUE) 以避免拒绝新提交的任务。newCachedThreadPool这就是所使用的策略。 2、无界队列。使用无界队列(通常使用预定义的容量)。 LinkedBlockingQueue理论上,缓冲队列可以为无限数量的任务排队)将导致全部。 corePoolSize 当所有线程都在工作时,将新任务添加到缓冲区队列。这样,创建的线程不会超过 corePoolSize,因此,maximumPoolSize 的值也无效。当每个任务完全独立于其他任务时,也就是说,使用无界队列是合适的。任务执行互不影响。newFixedThreadPool这就是所使用的策略。 3,有界队列。当使用受限的 maximumPoolSizes 如果为,则为有界队列(使用常规缓冲区队列)。ArrayBlockingQueue,并设置队列的最大长度),以帮助防止资源耗尽,但可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设置合理的参数。 六、比较Executor和new Thread() new Thread缺点如下: a. 每次new Thread新物件的性能很差。 b. 线程缺乏统一管理,可能会无限制地创建新的线程,相互竞争,并可能消耗过多的系统资源而导致崩溃或oom。 c. 缺少定时执行、周期性执行、线程中断等更多功能。 相比new Thread,Java提供的四个线程池的好处是: a. 重用现有线程以减少对象创建和终止的开销,并获得良好的性能。 b. 它可以有效控制并发线程的最大数量,提高系统资源的利用率,避免过度的资源竞争和拥塞。 c. 提供定时执行、常规执行、单线程、并发控制等功能。 ———————————————— 版权声明:本文是CSDN博主“不断前进的新秀”\_“原文,跟上。CC 4.0 BY-SA版权协议,转载请附上原始来源链接和本声明。 原始链接:https://blog.csdn.net/weixin\_40304387/article/details/80508236
版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门