美文网首页
Java - 线程池简介

Java - 线程池简介

作者: 打杂的_e2c9 | 来源:发表于2019-12-30 18:38 被阅读0次

    简介

    1. 什么是java线程池

    一个管理线程的池子,它帮我们我们管理线程,避免增加创建线程和销毁线程的资源损耗

    2. 线程池的优点

    重用线程池中的线程:避免因为线程的创建和销毁所带来的性能开销
    提高相应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
    提高线程的可管理性:线程是稀缺资源,如果无限制的创建不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

    相关类及架构图

    image.png
    • Executor:任务执行者,线程池中几乎所有的类都直接或者间接的实现了Executor,它是线程池框架的基础,它提供了一种将“任务提交”与“任务执行”分离开来的机制
    • ExecutorServices:它继承自Executor,它是“执行者服务接口”,添加了一些用来管理执行器生命周期和任务生命周期的方法
    • AbstractExecutorService:是一个抽象类,实现了ExecutorService 接口,为ExecutorService中的函数提供了默认实现
    • ThreadPoolExecutor:线程池的核心类,用来处理被提交的任务
    • ScheduledExecutorService:一个接口,它相当于提供了“延时”和“周期执行”功能的ExecutorService
    • ScheduledThreadPoolExecutor:一个实现类,可以在给定的延迟后执行任务,或者定期执行命令,比Timer灵活强大
    • Executors:它通过静态工厂方法返回ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 等类的对象。

    核心类ThreadPoolExecutor

    1. 构造方法及参数含义
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            ...
        }
    

    corePoolSize:核心线程数,默认情况下会一直存活,设置了allowCoreThreadTimeOut属性为true时,当等待时间超过 keepAliveTime时,核心线程数会被终止
    maximumPoolSize:线程池中最大的线程数,活动线程数达到这个数值后,后续的新任务会被阻塞
    keepAliveTime:非核心线程的闲置时的超时时长,超过这个时长,非核心线程就会被回收
    unit:超时时长的时间单位
    workQueue:任务队列
    threadFactory:线程工厂,为线程池提供创建新线程的功能
    RejectedExecutionHandler:拒绝策略

    2. 关键参数

    转自:https://juejin.im/entry/58fada5d570c350058d3aaad

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    

    ctl是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。
    下面再介绍下线程池的运行状态. 线程池一共有五种状态, 分别是:

    RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务;
    SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
    STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
    TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
    TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
    进入TERMINATED的条件如下:
    线程池不是RUNNING状态;
    线程池状态不是TIDYING状态或TERMINATED状态;
    如果线程池状态是SHUTDOWN并且workerQueue为空;
    workerCount为0;
    设置TIDYING状态成功。

    image.png

    ctl相关方法

    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    
    • runStateOf:获取运行状态;
    • workerCountOf:获取活动线程数;
    • ctlOf:获取运行状态和活动线程数的值。
    3.线程池初始化执行过程
    1. 未达到核心线程数时,会直接启动一个核心线程执行任务
    2. 线程池中的线程数已达到或超过核心线程数,任务会被插入到任务队列中排队等待执行
    3. 如果任务队列已满,且此时未达到线程池规定的最大值,那此时立即启动一个非核心线程来执行任务
    4. 如果线程数量已达到线程池规定的最大值,就拒绝执行此任务,执行拒绝策略

    代码:
    execute

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * clt记录着runState和workerCount
         */
        int c = ctl.get();
        /*
         * workerCountOf方法取出低29位的值,表示当前活动的线程数;
         * 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;
         * 并把任务添加到该线程中。
         */
        if (workerCountOf(c) < corePoolSize) {
            /*
             * addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
             * 如果为true,根据corePoolSize来判断;
             * 如果为false,则根据maximumPoolSize来判断
             */
            if (addWorker(command, true))
                return;
            /*
             * 如果添加失败,则重新获取ctl值
             */
            c = ctl.get();
        }
        /*
         * 如果当前线程池是运行状态并且任务添加到队列成功
         */
        if (isRunning(c) && workQueue.offer(command)) {
            // 重新获取ctl值
            int recheck = ctl.get();
            // 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,
            // 这时需要移除该command
            // 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
            if (! isRunning(recheck) && remove(command))
                reject(command);
            /*
             * 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
             * 这里传入的参数表示:
             * 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
             * 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
             * 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
             */
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        /*
         * 如果执行到这里,有两种情况:
         * 1. 线程池已经不是RUNNING状态;
         * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
         * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
         * 如果失败则拒绝该任务
         */
        else if (!addWorker(command, false))
            reject(command);
    }
    

    addWorker方法
    addWorker方法的主要工作是在线程池中创建一个新的线程并执行,firstTask参数 用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,代码如下:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取运行状态
            int rs = runStateOf(c);
    
            /*
             * 这个if判断
             * 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
             * 接着判断以下3个条件,只要有1个不满足,则返回false:
             * 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
             * 2. firsTask为空
             * 3. 阻塞队列不为空
             * 
             * 首先考虑rs == SHUTDOWN的情况
             * 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false;
             * 然后,如果firstTask为空,并且workQueue也为空,则返回false,
             * 因为队列中已经没有任务了,不需要再添加线程了
             */
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
    
            for (;;) {
                // 获取线程数
                int wc = workerCountOf(c);
                // 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
                // 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较,
                // 如果为false则根据maximumPoolSize来比较。
                // 
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 尝试增加workerCount,如果成功,则跳出第一个for循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 如果增加workerCount失败,则重新获取ctl的值
                c = ctl.get();  // Re-read ctl
                // 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
    
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 根据firstTask来创建Worker对象
            w = new Worker(firstTask);
            // 每一个Worker对象都会创建一个线程
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
                    // rs < SHUTDOWN表示是RUNNING状态;
                    // 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
                    // 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // workers是一个HashSet
                        workers.add(w);
                        int s = workers.size();
                        // largestPoolSize记录着线程池中出现过的最大线程数量
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    // 启动线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
    

    注意一下这里的t.start()这个语句,启动时会调用Worker类中的run方法,Worker本身实现了Runnable接口,所以一个Worker类型的对象也是一个线程。

    Worker类
    线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象,看一下Worker的定义:

    private final class Worker
            extends AbstractQueuedSynchronizer
            implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;
    
        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;
    
        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    
        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
    
        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.
    
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
    
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    
        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }
    
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }
    

    Worker类继承了AQS,并实现了Runnable接口,注意其中的firstTask和thread属性:firstTask用它来保存传入的任务;thread是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。

    在调用构造方法时,需要把任务传入,这里通过getThreadFactory().newThread(this);来新建一个线程,newThread方法传入的参数是this,因为Worker本身继承了Runnable接口,也就是一个线程,所以一个Worker对象在启动的时候会调用Worker类中的run方法。

    Worker继承了AQS,使用AQS来实现独占锁的功能。为什么不使用ReentrantLock来实现呢?可以看到tryAcquire方法,它是不允许重入的,而ReentrantLock是允许重入的

    runWorker方法
    在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的代码如下:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        // 获取第一个任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 允许中断
        w.unlock(); // allow interrupts
        // 是否因为异常退出循环
        boolean completedAbruptly = true;
        try {
            // 如果task为空,则通过getTask来获取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
    

    这里说明一下第一个if判断,目的是:

    如果线程池正在停止,那么要保证当前线程是中断状态;
    如果不是的话,则要保证当前线程不是中断状态;
    这里要考虑在执行该if语句期间可能也执行了shutdownNow方法,shutdownNow方法会把状态设置为STOP,回顾一下STOP状态:

    不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态。

    STOP状态要中断线程池中的所有线程,而这里使用Thread.interrupted()来判断是否中断是为了确保在RUNNING或者SHUTDOWN状态时线程是非中断状态的,因为Thread.interrupted()方法会复位中断的状态。

    总结一下runWorker方法的执行过程:

    while循环不断地通过getTask()方法获取任务;
    getTask()方法从阻塞队列中取任务;
    如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
    调用task.run()执行任务;
    如果task为null则跳出循环,执行processWorkerExit()方法;
    runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。
    这里的beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。

    completedAbruptly变量来表示在执行任务过程中是否出现了异常,在processWorkerExit方法中会对该变量的值进行判断。

    getTask方法
    getTask方法用来从阻塞队列中取任务,代码如下:

    private Runnable getTask() {
        // timeOut变量的值表示上次从阻塞队列中取任务时是否超时
        boolean timedOut = false; // Did the last poll() time out?
    
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
    
            // Check if queue empty only if necessary.
            /*
             * 如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
             * 1. rs >= STOP,线程池是否正在stop;
             * 2. 阻塞队列是否为空。
             * 如果以上条件满足,则将workerCount减1并返回null。
             * 因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。
             */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
    
            int wc = workerCountOf(c);
    
            // Are workers subject to culling?
            // timed变量用于判断是否需要进行超时控制。
            // allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
            // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
            // 对于超过核心线程数量的这些线程,需要进行超时控制
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    
            /*
             * wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
             * timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
             * 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
             * 如果减1失败,则返回重试。
             * 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
             */
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
    
            try {
                /*
                 * 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
                 * 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。
                 * 
                 */
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                // 如果 r == null,说明已经超时,timedOut设置为true
                timedOut = true;
            } catch (InterruptedException retry) {
                // 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
                timedOut = false;
            }
        }
    }
    

    这里重要的地方是第二个if判断,目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是timedOut为true的情况,说明workQueue已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,保持线程数量在corePoolSize即可。

    什么时候会销毁?当然是runWorker方法执行完之后,也就是Worker中的run方法执行完,由JVM自动回收。

    getTask方法返回null时,在runWorker方法中会跳出while循环,然后会执行processWorkerExit方法。

    processWorkerExit方法

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // 如果completedAbruptly值为true,则说明线程执行时出现了异常,需要将workerCount减1;
        // 如果线程执行时没有出现异常,说明在getTask()方法中已经已经对workerCount进行了减1操作,这里就不必再减了。  
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
    
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //统计完成的任务数
            completedTaskCount += w.completedTasks;
            // 从workers中移除,也就表示着从线程池中移除了一个工作线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
    
        // 根据线程池状态进行判断是否结束线程池
        tryTerminate();
    
        int c = ctl.get();
        /*
         * 当线程池是RUNNING或SHUTDOWN状态时,如果worker是异常结束,那么会直接addWorker;
         * 如果allowCoreThreadTimeOut=true,并且等待队列有任务,至少保留一个worker;
         * 如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize。
         */
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }
    

    至此,processWorkerExit执行完之后,工作线程被销毁,以上就是整个工作线程的生命周期,从execute方法开始,Worker使用ThreadFactory创建新的工作线程,runWorker通过getTask获取任务,然后执行任务,如果getTask返回null,进入processWorkerExit方法,整个线程结束,如图所示:


    【2,3部分转自:https://juejin.im/entry/58fada5d570c350058d3aaad
    4 线程池如何实现复用的

    线程重用的核心是,我们知道,Thread.start()只能调用一次,一旦这个调用结束,则该线程就到了stop状态,不能再次调用start。
    则要达到复用的目的,则必须从Runnable接口的run()方法上入手,可以这样设计这个Runnable.run()方法(就叫外面的run()方法):
    它本质上是个无限循环,跑的过程中不断检查我们是否有新加入的子Runnable对象(就叫内部的runnable:run()吧,它就是用来实现我们自己的任务),有就调一下我们的run(),其实就一个大run()把其它小run()#1,run()#2,...给串联起来了,基本原理就这么简单

    详细请看:https://www.cnblogs.com/myseries/p/10895078.html

    5 java 中自带的几个线程池
    • FixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    

    固定线程数的线程池:最大线程数和核心线程数相同,在默认设置时,线程不受keepAliveTime影响;使用的无界队列,则表示运行中不会拒绝任务,由于newFixedThreadPool只有核心线程,并且这些线程都不会被回收,也就是它能够更快速的响应外界请求

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

    单一任务线程池:保证任务按顺序执行,其他的参数和Fix 无异.这一个任务处于活动状态时,其他任务都会在任务队列中排队等候依次执行,所以在这个任务执行之间我们不需要处理线程同步的问题。

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

    缓冲线程池:是一个根据需求创建新线程的线程池,max 是无界的,提供了一个没有容量的队列,如果主线程提供任务的速度大于线程处理的速度,则会不断的创建线程,极端情况会耗尽cpu和内存资源,所以建议执行好事少的任务

    • SingleThreadScheduledExecutor
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
            return new DelegatedScheduledExecutorService
                (new ScheduledThreadPoolExecutor(1));
        }
    
    • ScheduledThreadPool
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    

    定期执行任务的线程池:可以根据给定的时间定期的执行任务

    使用方法

    ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
    service.schedule(new Runnable() {
        public void run() {
            System.out.println(Thread.currentThread().getName()+"延迟三秒执行");
        }
    }, 3, TimeUnit.SECONDS);
    service.scheduleAtFixedRate(new Runnable() {
        public void run() {
            System.out.println(Thread.currentThread().getName()+"延迟三秒后每隔2秒执行");
        }
    }, 3, 2, TimeUnit.SECONDS);
    

    schedule(Runnable command, long delay, TimeUnit unit):延迟一定时间后执行Runnable任务;
    schedule(Callable callable, long delay, TimeUnit unit):延迟一定时间后执行Callable任务;
    scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延迟一定时间后,以间隔period时间的频率周期性地执行任务;
    scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。

    线程池的使用技巧

    需要针对具体情况而具体处理,不同的任务类别应采用不同规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任务。(N代表CPU个数)

    CPU密集型任务:线程池中线程个数应尽量少,如配置N+1个线程的线程池。
    IO密集型任务:由于IO操作速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率,如2*N。
    混合型任务:可以拆分为CPU密集型任务和IO密集型任务,当这两类任务执行时间相差无几时,通过拆分再执行的吞吐率高于串行执行的吞吐率,但若这两类任务执行时间有数据级的差距,那么没有拆分的意义。

    相关文章

      网友评论

          本文标题:Java - 线程池简介

          本文链接:https://www.haomeiwen.com/subject/wncmoctx.html