线程池ThreadPoolExecutor

线程池

一、什么是线程池?为什么要用线程池?  

  1. 降低资源的消耗。降低线程创建和销毁的资源消耗;
  2. 提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
  3. 提高线程的可管理性。

二、线程池的创建,各个参数含义

ThreadPoolExecutor,jdk所有线程池实现的父类

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
  • int corePoolSize  :线程池中核心线程数,< corePoolSize  ,就会创建新线程;如果= corePoolSize  ,这个任务就会保存到BlockingQueue;刚创建线程池时,池里没有线程,如果调用prestartAllCoreThreads()方法就会一次性的启动corePoolSize个数的线程。
  • int maximumPoolSize, 允许的最大线程数,BlockingQueue也满了,< maximumPoolSize时候就会再次创建新的线程
  • long keepAliveTime, 线程空闲下来后,存活的时间,这个参数只在> corePoolSize才有用
  • TimeUnit unit, 存活时间的单位值
  • BlockingQueue<Runnable> workQueue, 保存任务的阻塞队列
  • ThreadFactory threadFactory, 创建线程的工厂,给新建的线程赋予名字
  • RejectedExecutionHandler handler  饱和策略的四种方法:
  1. AbortPolicy :直接抛出异常,默认;
  2. CallerRunsPolicy:用调用者所在的线程来执行任务
  3. DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务
  4. DiscardPolicy :当前任务直接丢弃

实现自己的饱和策略,实现RejectedExecutionHandler接口即可

三、提交任务及工作机制

execute(Runnable command)  不需要返回

Future<T> submit(Callable<T> task) 需要返回

execute方法:

工作机制

总结运行机制:

  1.线程池中线程数<corePoolSize,会直接创建新的线程。

  2.线程池中线程数>corePoolSize,且线程池中线程数<maximumPoolSize,加入BlockingQueue阻塞队列。

  3.线程池中线程数<maximumPoolSize,且BlockingQueue队列装满时,直接创建新线程。

  4.线程池中线程数>maximumPoolSize,则使用饱和策略。

  5.线程池中线程数>corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。

  6.设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。

四、关闭线程池(协作式)

shutdownNow():设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程

shutdown():设置线程池的状态,只会中断所有没有执行任务的线程

五、合理配置线程池

根据任务的性质来:计算密集型(CPU),IO密集型,混合型

计算密集型:加密,大数分解,正则……., 线程数适当小一点,最大推荐:机器的Cpu核心数+1,为什么+1,防止页缺失,获取数据时还在磁盘上,当前线程会被挂起;如果太大cpu调度上下文切换,导致性能下降。(机器的Cpu核心=Runtime.getRuntime().availableProcessors();)

IO密集型:读取文件,数据库连接,网络通讯, 线程数适当大一点,机器的Cpu核心数*2

混合型:尽量拆分,IO密集型>>计算密集型,拆分意义不大,IO密集型~计算密集型,拆分意义比较大

队列的选择上,应该使用有界,无界队列可能会导致内存溢出,OOM

六、预定义的线程池

newFixedThreadPool

创建固定线程数量的,适用于负载较重的服务器,使用了无界(LinkedBlockingQueue)队列,可能导致OOM;

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

newSingleThreadExecutor

创建单个线程,需要顺序保证执行任务,不会有多个线程活动,使用了无界队列

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

CachedThreadPool

会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueue(不存储任何数据)

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

WorkStealingPool(JDK7以后) 

基于ForkJoinPool实现

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

ScheduledThreadPoolExecutor 

需要定期执行周期任务,Timer不建议使用了。

newSingleThreadScheduledExecutor:只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务

newScheduledThreadPool 可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候

方法说明:

schedule:只执行一次,任务还可以延时执行

scheduleAtFixedRate:提交固定时间间隔的任务(每个任务互不干扰,间隔10s,第一个任务从0s开始,第二个从10s开始...)

scheduleWithFixedDelay:提交固定延时间隔执行的任务(第一个任务的结束时间和第二个任务的开始时间间隔;间隔10s,第一个任务从0s开始用了5s,第二个任务开始时间是15s)

建议在提交给ScheduledThreadPoolExecutor的任务要住catch异常。

七、Executor框架

八、实例

public class UseThreadPool {
	//工作线程
    static class Worker implements Runnable
    {
        private String taskName;
        private Random r = new Random();

        public Worker(String taskName){
            this.taskName = taskName;
        }

        public String getName() {
            return taskName;
        }

        @Override
        public void run(){
            System.out.println(Thread.currentThread().getName()
            		+" process the task : " + taskName);
            SleepTools.ms(r.nextInt(100)*5);
        }
    }
    
    static class CallWorker implements Callable<String>{
    	
        private String taskName;
        private Random r = new Random();

        public CallWorker(String taskName){
            this.taskName = taskName;
        }

        public String getName() {
            return taskName;
        }    	

		@Override
		public String call() throws Exception {
            System.out.println(Thread.currentThread().getName()
            		+" process the task : " + taskName);
            return Thread.currentThread().getName()+":"+r.nextInt(100)*5;
		}
    	
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException
    {
    	ExecutorService pool = new ThreadPoolExecutor(2,4,3,TimeUnit.SECONDS,
    			new ArrayBlockingQueue<Runnable>(10),
    			new ThreadPoolExecutor.DiscardOldestPolicy());
//    	ExecutorService pool = Executors.newCachedThreadPool();//相对来说用的比较少
    	for(int i=0;i<3;i++) {
    		Worker worker = new Worker("worker_"+i);
    		pool.execute(worker);
    	}
    	for(int i=0;i<3;i++) {
    		CallWorker callWorker = new CallWorker("callWorker_"+i);
    		Future<String> result = pool.submit(callWorker);
    		System.out.println(result.get());
    	}
    	pool.shutdown();
    }
}

ThreadPoolExecutor执行结果:

pool-1-thread-2 process the task : worker_1
pool-1-thread-1 process the task : worker_0
pool-1-thread-2 process the task : worker_2
pool-1-thread-2 process the task : callWorker_0
pool-1-thread-2:30
pool-1-thread-2 process the task : callWorker_1
pool-1-thread-2:195
pool-1-thread-2 process the task : callWorker_2
pool-1-thread-2:420
newCachedThreadPool执行结果:
pool-1-thread-1 process the task : worker_0
pool-1-thread-2 process the task : worker_1
pool-1-thread-3 process the task : worker_2
pool-1-thread-4 process the task : callWorker_0
pool-1-thread-4:420
pool-1-thread-4 process the task : callWorker_1
pool-1-thread-4:105
pool-1-thread-4 process the task : callWorker_2
pool-1-thread-4:280

 

九、CompletionService

CompletionService是Java8的新增接口,JDK为其提供了一个实现类ExecutorCompletionService。这个类是为线程池中Task的执行结果服务的,即为Executor中Task返回Future而服务的。CompletionService的实现目标是任务先完成可优先获取到,即结果按照完成先后顺序排序。

实例:

任务类随机休眠一段时间

public class WorkTask implements Callable<Integer> {
    private String name;
    public WorkTask(String name) {
        this.name = name;
    }

    @Override
    public Integer call() {
        int sleepTime = new Random().nextInt(1000);
        
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 返回给调用者的值
        return sleepTime;
    }
}

自己使用集合和CompletionService来获取线程池中任务的返回结果

public class CompletionCase {
    private final int POOL_SIZE = Runtime.getRuntime().availableProcessors();
    private final int TOTAL_TASK = Runtime.getRuntime().availableProcessors();

    // 方法一,自己写集合来实现获取线程池中任务的返回结果
    public void testByQueue() throws Exception {
    	long start = System.currentTimeMillis();
    	//统计所有任务休眠的总时长
    	AtomicInteger count = new AtomicInteger(0);
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        //容器存放提交给线程池的任务,list,map,
        BlockingQueue<Future<Integer>> queue = 
        		new LinkedBlockingQueue<Future<Integer>>();

        // 向里面扔任务
        for (int i = 0; i < TOTAL_TASK; i++) {
            Future<Integer> future = pool.submit(new WorkTask("ExecTask" + i));
            queue.add(future);//i=0 先进队列,i=1的任务跟着进
        }

        // 检查线程池任务执行结果
        for (int i = 0; i < TOTAL_TASK; i++) {
        	int sleptTime = queue.take().get();///i=0先取到,i=1的后取到
        	count.addAndGet(sleptTime);
        }

        // 关闭线程池
        pool.shutdown();
        System.out.println("self-------------tasks sleep time "+count.get()
        		+"ms,and spend time "
        		+(System.currentTimeMillis()-start)+" ms");
    }

    // 方法二,通过CompletionService来实现获取线程池中任务的返回结果
    public void testByCompletion() throws Exception {
    	long start = System.currentTimeMillis();
    	AtomicInteger count = new AtomicInteger(0);
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        CompletionService<Integer> cService = new ExecutorCompletionService<>(pool);
        
        // 向里面扔任务
        for (int i = 0; i < TOTAL_TASK; i++) {
        	cService.submit(new WorkTask("ExecTask" + i));
        }
        
        // 检查线程池任务执行结果
        for (int i = 0; i < TOTAL_TASK; i++) {
        	int sleptTime = cService.take().get();
        	count.addAndGet(sleptTime);
        }        

        // 关闭线程池
        pool.shutdown();
        System.out.println("CompletionService-------------tasks sleep time "+count.get()
			+"ms,and spend time "
			+(System.currentTimeMillis()-start)+" ms");
    }

    public static void main(String[] args) throws Exception {
        CompletionCase t = new CompletionCase();
        t.testByQueue();
        t.testByCompletion();
    }
}

运行结果:

self-------------tasks sleep time 3516ms,and spend time 985 ms
CompletionService-------------tasks sleep time 4369ms,and spend time 930 ms

总结 

  1. 使用自定义的容器时,假如第一个线程被阻塞,那么后面的线程会等待第一个线程执行完,后面线程的才能获取到结果。
  2. 使用CompletionService时,假如第一个线程被阻塞,后面的线程不会受第一个线程阻塞影响,依次可以获取到结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值