多线程技术点-JDK并发包-线程池-Executor框架

线程池概念:

  为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用,所以需要线程池。
线程池中,总有几个活跃线程,当你需要使用线程时,可以从池子中随便拿一个空闲线程,完成工作时,
并没有关闭线程,而是将这个线程退回到池子中,方便其他人复用。

 

线程池实现:

  JDK对线程池提供了一套Executor框架,帮助开发人员有效地进行线程池控制。

 

Executor框架详解

  实现: Executors类是线程池工厂的角色,通过Executors可以获取一个拥有特定功能的线程池。

       ThreadPoolExecutor实现了ExecutorService(继承Executor接口)接口,因此这个类实现了线程池调度的所有细节,
       Executors工厂类就是代理ThreadPoolExecutor获取实例。

  图解:

    

  

Executors工厂提供创建线程池实例的方法

  1、newFixedThreadPool()

  说明:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。

  若没有,则新的任务暂时存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

  代码

    

 1 package com.dsd.jdk.executor.executerservice;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 /**
 7  * 固定线程数量的线程池例子
 8  * @author daishengda
 9  *
10  */
11 public class ThreadPoolFixedDemo {
12     public static class MyTask implements Runnable {
13 
14         @Override
15         public void run() {
16             System.out.println(System.currentTimeMillis() + ":Thread ID:"
17                     + Thread.currentThread().getId());
18         }
19 
20     }
21 
22     public static void main(String[] args) {
23         MyTask task = new MyTask();
24         ExecutorService es = Executors.newFixedThreadPool(5);
25         for (int i = 0; i < 5; i++) {
26             es.submit(task);
27         }
28     }
29 }

 

  2、newSingleThreadExecutor()

  说明:该方法返回只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存到一个任务队列中,待线程空闲,

  按先入先出的顺序执行队列中的任务。

  3、newCachedThreadExector()

  说明:该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会有些使用可复用的线程。

  若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

  4、newSingleThreadScheduledExecutor()

  说明:该方法返回一个ScheduleExecutorService对象,线程池大小为1。ScheduleExecutorService接口在ExecutorService接口之上扩展了在
  给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务,实现定时任务功能。

  5、newScheduleExecutor()

  说明:该方法也返回一个ScheduleExecutorService对象,该线程池可以指定线程数量。

  ScheduleExecutorService对象方法:

   

    /**
     * 会在给定时间,对任务进行一次调度
     * @param command 待调度线程
     * @param delay 延迟时间
     * @param unit 单位
     * @return
     */
    public ScheduledFuture<?> schedule(Runnable command,
            long delay, TimeUnit unit);


    /**
     * 对任务进行周期性的调度
     * @param command 待调度线程
     * @param initialDelay 初始延迟时间(第一个线程的延迟时间)
     * @param period 时间间隔
     * @param unit 单位
     * @return
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                           long initialDelay,
                           long period,
                           TimeUnit unit);
    
    /**
     * 对任务进行周期性的调度
     * @param command 待调度线程
     * @param initialDelay 初始延迟时间(第一个线程的延迟时间)
     * @param delay 延迟时间间隔
     * @param unit 单位
     * @return
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                              long initialDelay,
                              long delay,
                              TimeUnit unit);

  注意:

    scheduleAtFixedRate与scheduleWithFixedDelay方法是有区别的。

  对于scheduleAtFixedRate,任务调度的频率是一定的,是以上一个任务开始执行的时间为起点,之后的period时间,调度下一次任务。

  对于scheduleWithFixedDelay,实在上一次任务结束后,再经过delay时间进行任务调度。

  所以两者的计算起点不一致。

    例子

 1 package com.dsd.jdk.executor.executerservice;
 2 
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.ScheduledExecutorService;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 /**
 8  * 任务调度线程池例子
 9  * @author daishengda
10  *
11  */
12 public class ScheduleExecutorServiceDemo {
13 
14     public static void main(String[] args) {
15         ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
16         ses.scheduleAtFixedRate(new Runnable() {
17             
18             @Override
19             public void run() {
20                 try {
21                     TimeUnit.SECONDS.sleep(1);
22                     System.out.println("FixedRate:"+System.currentTimeMillis()/1000);
23                 } catch (InterruptedException e) {
24                     e.printStackTrace();
25                 }
26             }
27         }, 0, 2, TimeUnit.SECONDS);
28         
29         ses.scheduleWithFixedDelay(new Runnable() {
30             
31             @Override
32             public void run() {
33                 try {
34                     TimeUnit.SECONDS.sleep(1);
35                     System.out.println("FixedDelay:"+System.currentTimeMillis()/1000);
36                 } catch (InterruptedException e) {
37                     e.printStackTrace();
38                 }
39             }
40         }, 0, 2, TimeUnit.SECONDS);
41     }
42 }

  

核心线程池的内部实现-ThreadPoolExecutor

  以上前三种类型的线程池,实际是对ThreadPoolExecutor类的封装。

  

    /**
     * 返回一个corePoolSize与maximumPoolSize大小一样,并且使用LinkedBlockingQueue任务队列的线程池,
     * 对于固定大小的线程池,不存在线程数量的动态变化,缺点是当任务提交非常频繁的时候,该队列可能迅速膨胀,
     * 从而耗尽系统资源。
     * @param nThreads 允许线程池存在的线程数
     * @return 线程池对象
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    /**
     * 是newFixedThreadPool方法的一种退化,只是简单的将线程池线程数量设置为1。
     * @return 线程池对象
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    /**
     * 返回一个corePoolSize为0,maximumPoolSize无穷大的线程池,意味着在没有任务时,该线程池内无线程,
     * 而当任务被提交时,该线程池会使用空闲的线程执行任务,若无空闲线程,则将任务加入SynchronousQueue队列,
     * 而且SynchronousQueue是直接提交的队列,它总是迫使线程池增加新的线程执行任务。
     * 当任务执行完毕,由于corePoolSize为0,因此空闲线程又会在指定时间内(60秒)被回收
     * @return
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

 

 ThreadPoolExecutor构造函数代码

    /**
     * ThreadPoolExecutor构造函数
     * 
     * @param corePoolSize 指定了线程池中的线程数量
     * @param maximumPoolSize 指定了线程池中的最大线程数量
     * @param keepAliveTime 当线程池数量超过corePoolSize时,多余的空闲线程的存活时间。
     *        即,超过corePoolSize的空闲线程,在多长时间内,会被销毁。
     * @param unit keepAliveTime的单位
     * @param workQueue 任务队列,被提交但尚未被执行的任务
     * @param threadFactory 线程工厂,用于创建线程,一般用默认即可
     * @param handler 拒绝策略。当任务太多来不及处理,如何拒绝任务。
     */
    public ThreadPoolExecutor(int corePoolSize, 
            int maximumPoolSize,
            long keepAliveTime, 
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue, 
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler) {
        if (corePoolSize < 0 || maximumPoolSize <= 0
                || maximumPoolSize < corePoolSize || keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

ThreadPoolExecutor任务调度逻辑

 调度逻辑场景说明

1、直接提交的队列SynchronousQueue,没有容量,每一个插入操作都要等待一个删除操作,反之,每一个删除也要等待插入操作。如果使用SynchronousQueue,提交的任务不会真实保存,而是将新任务提交给线程执行。如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经达到最大值,则执行拒绝策略。因此使用该队列,通常需要设置很大的maximumPoolSize,,否则容易触发拒绝策略。

2、有界的任务队列:如ArrayBlockingQueue队列。构造函数必须带一个容量参数,表示最大容量。当使用有界任务队列,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则优先创建新的线程,若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的任务执行任务。若大于maximumPoolSize,则执行拒绝策略。

3、无界的任务队列:如LinkedBlockingQueue类。与有界队列相比,除非系统资源耗尽,否则不会存在提交到等待队列失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,线程池会生成新的线程执行任务,但当线程池的线程数达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。

4、优先任务队列:优先任务队列是带有执行优先级的队列。它可以通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。ArrayBlockingQueue、LinkedBlockingQueue都是按照先进先出算法处理任务的。而PriorityBlockingQueue可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能有很好的质量保证。

ThreadPoolExecutor线程池的核心调度代码

    /**
     * 代码第9行的workerCountOf()函数取得了当前线程池的线程总数。当线程池总数小于corePoolSize核心线程数时,
     * 会将任务通过addWorker()直接调度执行。否则在第16行代码处(workQueue.offer())加入等待队列。
     * 如果进入等待队列失败(如有界队列达到上限或者使用了SynchronousQueue),则会执行第25行,将任务直接提交给线程池。
     * 如果当前线程池已经达到maximumPoolSize,则提交失败,执行第27行的拒绝策略。
     * 
     * 
     * @param command
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

 线程池的拒绝策略

  ThreadPoolExecutor构造函数最后一个参数是拒绝策略.。是系统超负荷运行时的补救措施,也就是线程池中的线程已经用完了,无法继续为新任务服务,同时等待队列中也已经排满了,无法加入新人物了,需要提供一套机制。

  JDK内置的拒绝策略

  • AbortPolicy策略:该策会直接抛出异常,阻止系统正常工作。
  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是。任务提交线程的性能极有可能会急剧下降。
  • DiscardOldestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  • DiscardPolicy策略:该策略默默地丢弃无法处理的任务,不予任务处理。如果允许任务丢失,这种方案最好。
    /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always.
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
    
    /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
    
    /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

注:以上内置策略实现了RejectedExecutionHandler接口,若还是无法满足实际应用需要,可以通过实现RejectedExecutionHandler接口进行扩展

自定义策略代码

package com.dsd.jdk.executor.policy;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 自定义线程池和拒绝策略使用例子
 * @author daishengda
 *
 */
public class RejectThreadPoolDemo {

    public static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId());
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        ExecutorService es = new ThreadPoolExecutor(5, 5, 
                    0L, TimeUnit.MILLISECONDS, 
                    new LinkedBlockingQueue<Runnable>(10),
                    Executors.defaultThreadFactory(),
                    new RejectedExecutionHandler() {
                        
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println(r.toString()+" is discard");
                        }
                    }
                );
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            es.submit(task);
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }
}

 ThreadFactory-ThreadPoolExecutor参数

  用途:是一个接口,方法有Thread newThread(Runnable r),用来创建线程。

  JDK默认使用的实现类DefaultThreadFactory

    /**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

 扩展线程池

  ThreadPoolExecutor提供了beforeExecute()、afterExecute()和terminated()三个接口对线程池进行扩展

  beforeExecute():运行任务前执行方法。

  afterExecute():运行任务结束后执行方法。

  terminated():线程池退出执行方法。

 

package com.dsd.jdk.Executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 通过复写线程池ThreadPoolExecutor的三个方法进行扩展,
 * 对线程池运行状态的跟踪,输入一些有用调试信息,方便定位问题
 * @author daishengda
 *
 */
public class ExtendThreadPool {

    public static class MyTask implements Runnable{

        public String name;
        
        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("正在执行"+":Thread ID:"+Thread.currentThread().getId()+
                    ",Task Name="+name);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
    
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()){

            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行:"+((MyTask)r).name);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("执行完成:"+((MyTask)r).name);
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
            
            
        };
        for (int i = 0; i < 5; i++) {
            MyTask task = new MyTask("TASK-GEYM-"+i);
            es.execute(task);
            TimeUnit.MILLISECONDS.sleep(10);
        }
        /**
         * 关闭线程池,比较安全优雅的关闭方法,它会等待所有任务执行完成后,再关闭线程池,但它并不会等待所有线程执行完成后再返回
         * 因此,可以简单理解为只是发送了一个关闭信号而已。shutdown()执行后,这个线程池就不能再接收其他新的任务了。
         */
        es.shutdown();
    }
}

 

转载于:https://www.cnblogs.com/daishengda/p/8904005.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值