JAVA并发编程-Executor框架与线程池

通过Executor来启动线程比使用Thread的start方法更好,除了
更易管理
效率更好(用线程池实现,节约开销)
关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中


ExecutorService的生命周期包括三种状态:运行、关闭、终止。


shutdown()->状态关闭-> ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完后->终止状态


4种方法ThreadFactory建立的线程

newCachedThreadPool() :
缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中;
缓存型池子通常用于执行一些生存期很短的异步型任务, 因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是Executor的首选;
能reuse的线程,必须是timeout IDLE内的池中线程,缺省     timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池;
注意,超过TIMEOUT不活动,其会自动被终止


newFixedThreadPool(int):
newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程;
独特: 任意时间点,最多只能有固定数目的活动线程存在 此时如果有新的线程要建立,只能放在另外的队列中等待 直到当前的线程中某个线程终止直接被移出池子;
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)


实际运用中:
一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool




Executor执行Runnable任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestCachedThreadPool{
     public static void main(String[] args){
         ExecutorService executorService = Executors.newCachedThreadPool();
//      ExecutorService executorService = Executors.newFixedThreadPool(5);
//      ExecutorService executorService = Executors.newSingleThreadExecutor();
         for ( int i = 0 ; i < 5 ; i++){
             executorService.execute( new TestRunnable());
             System.out.println( "************* a" + i + " *************" );
         }
         executorService.shutdown();
     }
}
 
class TestRunnable implements Runnable{
     public void run(){
         System.out.println(Thread.currentThread().getName() + "线程被调用了。" );
     }
}


pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务

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<Future<String>> resultList = new ArrayList<Future<String>>();
 
         //创建10个任务并执行
         for ( int i = 0 ; i < 10 ; i++){
             //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
             Future<String> future = executorService.submit( new TaskWithResult(i));
             //将任务执行结果存储到List中
             resultList.add(future);
         }
 
         //遍历任务的结果
         for (Future<String> 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<String>{
     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();
     }
}

submit也是首先选择空闲线程来执行任务
,如果没有,才会创建新的线程来执行任务。
另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待
,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回


public ThreadPoolExecutor (
int corePoolSize,
int maximumPoolSize,
long   keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
)

corePoolSize:线程池中所保存的核心线程数,包括空闲线程。

maximumPoolSize:池中允许的最大线程数。

keepAliveTime:线程池中的空闲线程所能持续的最长时间。

unit:持续时间的单位。

workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。

总结:当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池中的线程数量是否大于maximumPoolSize。另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。


public static ExecutorService newCachedThreadPool() {
     return new ThreadPoolExecutor( 0 , Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>());
}
corePoolSize:0
maximumPoolSize: Integer.MAX_VALUE
keepAliveTime: 60L
由于核心线程数为0,因此每次添加任务,都会先从线程池中找空闲线程,
如果没有就会创建一个线程来执行新的任务,并将该线程加入到线程池中


public static ExecutorService newFixedThreadPool( int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
}
corePoolSize: nThreads
maximumPoolSize: nThreads
keepAliveTime: 0L

线程池的大小的固定,不会动态地扩大;
另外,keepAliveTime设定为了0,也就是说线程只要空闲下来,就会被移除线程池



排队的策略:

直接提交  newCachedThreadPool SynchronousQueue:将任务直接交给线程处理,如果线程都在工作,则构造新线程处理任务,
要求无界 maximumPoolSizes(Integer.MAX_VALUE) 以避免拒绝新提交的任务

无界队列    newFixedThreadPool
采用预定义容量的 LinkedBlockingQueue,理论上是该缓冲队列可以对无限多的任务排队
当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列


有界队列
当使用有限的 maximumPoolSizes 时,有界队列(一般缓冲队列使用ArrayBlockingQueue,并制定队列的最大长度)有助于防止资源耗尽,
但是可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设定合理的参数


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值