JUC之线程池

创建线程池由工厂类Executors完成。其中newCachedThreadPool,newFixedThreadPool,newSingleThreadExecutor对应的实现类是ThreadPoolExecutor,newScheduledThreadPool对应的实现类是ScheduledThreadPoolExecutor。

ThreadPoolExecutor

这里写图片描述

  • int corePoolSize 线程池的最小线程数目,即使线程闲置,这些线程也一直保留

  • int maximumPoolSize 线程池中最大线程数目

  • long keepAliveTime 当线程池中的线程超过corePoolSize时,当超过该事件设定的时长线程一直处于闲置状态时,线程将被回收

  • TimeUnit unit 上述值对应的时间单位

  • BlockingQueue workQueue 一个保留Runable对象的阻塞队列。
    相关实现可以参考
    https://blog.csdn.net/define_us/article/details/80222062

  • ThreadFactory threadFactory 用于创建线程的工厂。该值可以缺省,此时使用的是默认的线程创建工厂。

    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;
        }
    }
  • RejectedExecutionHandler handler 当线程池中的线程已经达到maximumPoolSize同时BlockingQueue也已经满员时,调用该handler。该值可以缺省,将采用如下默认实现
    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());
        }
    }

ThreadPoolExecutor该类中,最核心的方法应该就是execute()了。

  • 如果当前线程数少于corePoolSize,那么就直接新建线程。无非是JDK基本的线程操作代码。
			//构造方法中调用ThreadFactory新建一个线程。并将worker的firstTask指定为当前task。
            w = new Worker(firstTask);
            final Thread t = w.thread;
            t.start();
            //加入worker列表
            workers.add(w);
  • 将命令加入队列。在加入队列后,我们仍然需要确认一下是否还有存活线程。如果没有了,还需要重新创建线程。

  • 如果我们不能成功的把命令加入队列,也不能新建线程,那么就只能reject这个命令
    完整逻辑如下

		//获取当前atomic integer ctl中记载的线程池的状态(实际上用比特操作同时存储了状态和线程数量)。
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
        	//如果小于,那么增加一个worker,处理新的command
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //isRunning(c)用于检查是否处于shutdown状态
        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);

这里面Worker最难以理解的地方是其继承了AbstractQueuedSynchronizer。可以堪称woker的执行是一种资源。所以不能排出一个worker会被两个thread抢夺(当然从代码逻辑上来讲这是不可能的)。所以需要借助AQS的资源排队机制。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable

        /**
         * 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);//新建了一个线程,并把自己Worker作为一个Runable对象传给这个thread。这样,这个thread在start的时候就会执行这个Worker的方法。进一步调用到ThreadPoolExecutor的runwoker方法。
        }

注释里写了一个解释

    /**
     * Class Worker mainly maintains interrupt control state for
     * threads running tasks, along with other minor bookkeeping.
     * This class opportunistically extends AbstractQueuedSynchronizer
     * to simplify acquiring and releasing a lock surrounding each
     * task execution.  This protects against interrupts that are
     * intended to wake up a worker thread waiting for a task from
     * instead interrupting a task being run.  We implement a simple
     * non-reentrant mutual exclusion lock rather than use
     * ReentrantLock because we do not want worker tasks to be able to
     * reacquire the lock when they invoke pool control methods like
     * setCorePoolSize.  Additionally, to suppress interrupts until
     * the thread actually starts running tasks, we initialize lock
     * state to a negative value, and clear it upon start (in
     * runWorker).
     */

我们看一下Worker的实际运行过程(ThreadPoolExecutor的runwoker方法)。无非是从队列拿task,拿去执行,如果报出异常就把那么就执行processWorkerExit。这里面我们也可以看到,线程池里的线程,是不会因为抛出异常而死亡的。因为已经捕获了所有的异常。

    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);
        }
    }

JAVA内部的几种线程池

ScheduledThreadPoolExecutor

这里写图片描述
构造函数如下:

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。核心是使用DelayedWorkQueue。DelayedWorkQueue是在该类内部定义的一个静态内部类,实现了BlockingQueue接口。
首先你需要知道算法,堆可以用来实现优先队列。DelayedWorkQueue就是使用了堆,插入元素使用siftUp方法,移除元素使用siftDown方法。在该对象中,维护了一个Condition。通过available.awaitNanos(delay)来消耗时间。

        private final Condition available = lock.newCondition();
        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)
                            //finishPoll用于在出队时调整堆结构
                            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();
            }
        }

任务队列中存储的是ScheduledFutureTask
在这里插入图片描述

SingleThreadScheduledExecutor

单例的定时线程池

FixedThreadPool

核心线程数和最大线程数相同

SingleThreadExecutor

一个单线程的线程池

CachedThreadPool

maximumPoolSize设置为无穷大。所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程。

ForkJoinPool

将一个大任务拆分成多个小任务后,使用fork可以将小任务分发给其他线程同时处理,使用join可以将多个线程处理的结果进行汇总;这实际上就是分治思想的并行版本。

在这里插入图片描述

提交的任务必须是的对象RecursiveTask

线程池的强大应用

我们并非是没事闲的增加了线程池的概念。线程池给予了我们强大而常用的功能。

UncaughtExceptionHandler

如果不使用线程池,为了捕获线程抛出的异常,你只能每个线程作如下操作

Thread thread = new Thread(new ThreadTest1()); 
thread.setUncaughtExceptionHandler(new MyExceptionHandler());
thread.start();

有了线程池,我们可以非常方便的实现这一功能。

ThreadFactory executorThreadFactory = new BasicThreadFactory.Builder()
        .namingPattern("task-scanner-executor-%d")
        .uncaughtExceptionHandler(new LogUncaughtExceptionHandler(LOGGER))
        .build();
Executors.newSingleThreadExecutor(executorThreadFactory);
线程池的关闭

阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。
**shutdown()**阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。
通过将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。
在调用中断任务的方法时,它会检测workers中的任务,如果worker对应的任务没有中断,并且是空闲线程,它才会去中断。另外的话,workQueue中的值,还是按照一定的逻辑顺序不断的往works中进行输送的,这样一来,就可以保证提交的任务按照线程本身的逻辑执行,不受到影响。

**shutdownNow()**会阻止新来的任务提交,同时会中断当前正在运行的线程,即workers中的线程。另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。通过将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。当执行shutdownNow()方法时,如遇已经激活的任务,并且处于阻塞状态时,shutdownNow()会执行1次中断阻塞的操作,此时对应的线程报InterruptedException,如果后续还要等待某个资源,则按正常逻辑等待某个资源的到达。例如,一个线程正在sleep状态中,此时执行shutdownNow(),它向该线程发起interrupt()请求,而sleep()方法遇到有interrupt()请求时,会抛出InterruptedException(),并继续往下执行。在这里要提醒注意的是,在激活的任务中,如果有多个sleep(),该方法只会中断第一个sleep(),而后面的仍然按照正常的执行逻辑进行。

FAQ

  • 线程池的大概工作过程
    下面这个代码会打印begin job多少次。
public class ThreadPoolTest {
    public static void main(String[] args) {
        System.out.println("Hello,world");
        ExecutorService threadPoolExecutor = new ThreadPoolExecutor(5,20,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(new Job());
        }

    }

    static class Job implements Runnable {

        @Override
        public void run() {
            System.out.println("begin job");
            try{
                Object obj = new Object();
                synchronized (obj){
                    obj.wait();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

答案是八次。随着前五次任务提交,线程池依次启动了五个核心线程。第6个和第7个任务会进入缓冲队列。第8,第9,第10个因为队列已经满了,直接新建线程执行。然而,因为所有线程都会被阻塞,永远不会退出,所以,最终只能执行8次。缓冲队列中的两个线程因为没有线程退出而永远执行不到。(所以你也看到了这种情况下不保证线程池对任务按照提交顺序执行)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值