线程池及4种线程池的区别

线程池源码分析,我这里就不班门弄斧了,请参考占小狼大佬的博客。我在这里说说4种线程池吧。

newFixedThreadPool

初始化一个指定线程数的ThreadPoolExecutor对象(可以称为线程池,但是其中还没有创建工作线程),其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。执行work线程的代码:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            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);
        }
    }

如果是没有可执行任务,这里的while是会一直循环的,原因在于getTask方法

private Runnable getTask() {
        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.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

timed 这个属性在这里为false,allowCoreThreadTimeOut 为false,wc是当前worker线程的数量,不可能大于corePoolSize大小(暨创建newFixedThreadPool的时候传入的那个int参数),所以timed 为false,三目表达式会执行 workQueue.take()。线程会一直阻塞到这里。

newSingleThreadExecutor

newSingleThreadExecutor是newFixedThreadPool(1)的特例:

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

但是这里也有区别,这里使用了FinalizableDelegatedExecutorService包装了newFixedThreadPool,FinalizableDelegatedExecutorService只是暴露了ExecutorService接口的方法,这样可以使得这个newSingleThreadExecutor一但初始化,ThreadPoolExecutor的属性就不可变了,例子如下:

 ExecutorService service2=Executors.newSingleThreadExecutor();
        service2.execute(()-> {
            System.out.println(1111);
            throw new IllegalStateException("Error");
            });
        ((ThreadPoolExecutor)service2).setCorePoolSize(4);

运行时会报类型转化异常,如果换成newFixedThreadPool就可以正常运行。
newSingleThreadExecutor只会创建一个工作线程,如果线程运行中抛出异常或者中断,那么之后execute任务的时候会再一次创建一个worker工作线程。

其他的任何地方,newSingleThreadExecutor和newFixedThreadPool(1)并无区别,并且可以按execute的顺序执行多个任务。

newCachedThreadPool

1初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
2和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销,参看代码:

private Runnable getTask() {
        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.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

仍然查看getTask方法,这时corePoolSize=0,wc >corePoolSize为true,所以timed为true,之后会调用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) ,这里会阻塞一个keepAliveTime(默认60秒)时间,然后跳出runwork的while循环,结束线程。
鉴于此,使用该线程池时,一定要注意控制并发的任务数,否则短时间内创建大量的线程可能导致严重的性能问题。

newScheduledThreadPool

初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池实现定时任务。源码分析一波,他的调度过程,首先是初始化的存放task的队列是DelayedWorkQueue,它继承了AbstractQueue
实现了BlockingQueue接口;找到他的take方法:

public RunnableScheduledFuture take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture first = queue[0];
                    if (first == null)
                        available.await();
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        if (leader != null)
                            available.await();
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                available.awaitNanos(delay);
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

主要流程就是获取task,如果没有task就阻塞住,如果有task就阻塞delay这么久。long delay = first.getDelay(NANOSECONDS);这行代码获取时间:

public long getDelay(TimeUnit unit) {
            return unit.convert(time - now(), NANOSECONDS);
        }

ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}
time就是首次执行的延迟时间,period就是任务执行的间隔时间,那么这个时间是在哪里设置的呢?回到scheduleAtFixedRate方法:

public ScheduledFuture scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask sft =
            new ScheduledFutureTask(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

查看ScheduledFutureTask的run方法:

public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
    }
    private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

这里是真正执行任务的地方, setNextRunTime()里面设置了这个时间,这个时间减去现在的时间就是阻塞队列等待的时间;ScheduledFutureTask.super.runAndReset()执行完成之后返回true,之后再一次 reExecutePeriodic(outerTask):

void reExecutePeriodic(RunnableScheduledFuture task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

把这个任添加到阻塞队列里面,这样工作线程每次getTask都会获取到task,while 循环会一直执行。

另外scheduleAtFixedRate和scheduleWithFixedDelay的区别

关于这个问题,网上很多帅哥美女都有写blog来解释。但是,根据源代码来看:

 public ScheduledFuture scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask sft =
            new ScheduledFutureTask(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }
public ScheduledFuture scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask sft =
            new ScheduledFutureTask(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(-delay));
        RunnableScheduledFuture t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

初始化的时候只有一个地方有区别那就是创建ScheduledFutureTask对象时,传入的第三个参数。其他所有地方都是一样的。这个第三个参数只是个周期时间而已,负的和正的基本没区别。所以说,这两个方法基本没区别。注意,这两个方法没考虑,task执行时间超过周期时间的情况。所以,这个线程池的调度慎用。

总结

水平有限,还请看帖的大佬轻喷和指正。

你可能感兴趣的:(Java源码分析,Java,线程池)