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); }注释写的很清楚
- step1:如果当前池中已经存在的线程比corePoolSize数量少,那么直接用addWorker这个方法new一个新线程,并将当前任务交给这个新线程。addWorker方法会检查线程池的运行状态和当前存活的线程数量,如果不应该新增线程则返回false,进入step2
- step2:如果能将任务加到Queue中,仍需要做第二次检测,以防止在step1中检查的时候刚好有一个存活的线程在进入step2中销毁掉了,这种情况就要新new一个线程;还需要判断线程池是否在running,如果stop掉了,则将刚才入队的任务从队列中删除,因为线程池如果被中断,后面加进来的任务一律不运行,否则进入step3
- 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不希望上层应用修改这些值,因此做了一层包装,掩盖了这些方法
以上
阅读全文
0 0
- Java线程:线程池
- java--线程--线程池
- Java线程(六):线程池
- Java线程(五):线程池
- Java线程(五):线程池
- Java线程_07_线程池
- Java线程(五):线程池
- Java线程(六):线程池
- Java线程(五):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(六):线程池
- Java线程(五):线程池
- Java线程(六):线程池
- 2017.06.28 Gabor滤波器总结
- ORACLE优化方案 (转)(突然发现好多问题是版本问题,请看下一条)
- 欢迎使用CSDN-markdown编辑器
- Zookeeper 为什么要安装奇数节点
- grpc应用于微服务的分析,基于python
- JAVA 线程池
- S2分班考试笔试题总结
- MySQL创建高性能的索引
- boost log库学习使用三(输出日志到文件)
- poll f服务器
- Linux下网络性能评估
- eclipse中工程树形界面后方显示svn提交人(转)
- C++中的类和对象
- 【量化投资】基金择时策略浅析(2)-有择时能力的基金