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 extends Callable> 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
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除