JAVA 线程池

来源:互联网 发布:算法工程师笔试考什么 编辑:程序博客网 时间:2024/06/05 11:15

池化资源使得资源可以重复利用,提高响应速度,例如常见的数据库连接池。

我们不用手动创建和销毁线程,池会帮我们管理,另外还可以对线程监控。

理解线程的时候,要把线程Thread当作一个载体,而Runnable是一个任务,任务完成后,线程还可以执行其他任务


线程类相关的UML类图


最重要的是中间那个ThreadPoolExecutor,简单说下ScheduledThreadPoolExecutor和ForkJoinPool

ScheduledThreadPoolExecutor是一个可调度的线程池,可以设置多久之后线程开始运行,以及运行的频率

ForkJoinPool是一个支持并行化的线程池,接受ForkJoinTask,将一个任务分拆成多个子任务,每个任务分配到不同的cpu上执行,计算结果再通过join去合并


最下面的Executors是一个工具类,用来生成不同类型的线程池,最常用的就是以下三种:

  • newFixedThreadPool:固定大小
  • newCachedThreadPool:无界线程池,可以进行自动线程回收
  • newSingleThreadExecutor:单个后台线程池
这几种类型的线程池底层都是ThreadPoolExecutor,这个类有以下属性:
  • int corePoolSize :线程池基本大小
  • int maximumPoolSize :线程池最大大小
  • long keepAliveTime :单个线程保持活动时间
  • TimeUnit unit :保持活动时间单位
  • BlockingQueue workQueue :任务等待队列
  • ThreadFactory threadFactory :线程工厂
  • RejectedExecutionHandler handler :驳回回调
这个类里有最重要的一个方法void execute(Runnable),先理解了这个方法,对理解上面三种类型的线程池大有益处

execute()方法

public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        /*         * Proceed in 3 steps:         *         * 1. If fewer than corePoolSize threads are running, try to         * start a new thread with the given command as its first         * task.  The call to addWorker atomically checks runState and         * workerCount, and so prevents false alarms that would add         * threads when it shouldn't, by returning false.         *         * 2. If a task can be successfully queued, then we still need         * to double-check whether we should have added a thread         * (because existing ones died since last checking) or that         * the pool shut down since entry into this method. So we         * recheck state and if necessary roll back the enqueuing if         * stopped, or start a new thread if there are none.         *         * 3. If we cannot queue task, then we try to add a new         * thread.  If it fails, we know we are shut down or saturated         * and so reject the task.         */        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);    }
注释写的很清楚
  1. step1:如果当前池中已经存在的线程比corePoolSize数量少,那么直接用addWorker这个方法new一个新线程,并将当前任务交给这个新线程。addWorker方法会检查线程池的运行状态和当前存活的线程数量,如果不应该新增线程则返回false,进入step2
  2. step2:如果能将任务加到Queue中,仍需要做第二次检测,以防止在step1中检查的时候刚好有一个存活的线程在进入step2中销毁掉了,这种情况就要新new一个线程;还需要判断线程池是否在running,如果stop掉了,则将刚才入队的任务从队列中删除,因为线程池如果被中断,后面加进来的任务一律不运行,否则进入step3
  3. step3:如果我们不能将任务加到Queue中,就试着new一个新线程,如果失败了,那就说明Queue饱和了,或者线程池shut down

如果execute失败,则要运行reject方法,根据不同的RejectedExecutionHandler 执行不同的操作,ThreadPoolExecutor默认的RejectedExecutionHandler 是AbortPolicy,即抛出异常,还有其余三种实现有兴趣的童鞋可以去看看

了解execute是如何工作的,再来看那三种线程池

newFixedThreadPool

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

可以看到newFixedThreadPool的corePoolSize和maximumPoolSize的值都是nThreads;keepAliveTime设置为0,因为当newFixedThreadPool实例化时,线程池中的线程数量就已经固定,不存在线程回收的问题;workQueue 选择了LinkedBlockingQueue,是一个无界的队列,在execute时,可以无限向Queue中添加任务,这有可能会消耗掉所有内存。也就是说,任务数如果大于corePoolSize,则多出的任务就得在Queue中排队等待,直到有线程执行完,才去Queue中取任务执行

newCachedThreadPool

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

无界线程当然大小不能为固定值了,要设置为0,maximumPoolSize要设置成最大值MAX_VALUE,workQueue 选择了SynchronousQueue,这个Queue有个特点,在添加的同时必须有其他线程取走才能继续添加,因此在execute中向这个Queue执行offer操作永远是失败的,也就是说,newCachedThreadPool会一直走到step3才能创建新线程,但是无止尽的创建线程肯定是对资源的浪费,因此keepAliveTime设置成了60秒,即空闲线程超过60秒的自动销毁

newSingleThreadExecutor

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

其实就是newFixedThreadPool的特殊版,corePoolSize和maximumPoolSize的值都设为1,但这里有个有意思的地方,ThreadPoolExecutor被FinalizableDelegatedExecutorService修饰了,因为ThreadPoolExecutor有些方法例如setCorePoolSize、setMaximumPoolSize,而newSingleThreadExecutor不希望上层应用修改这些值,因此做了一层包装,掩盖了这些方法



以上