多线程编程
Excutors
Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可;
创建线程的几种方法
- newFixedThreadPool:实际执行ThreadPoolExecutor类(LinkedBlockingQueue)
- newSingleThreadExecutor:实际执行ThreadPoolExecutor类(LinkedBlockingQueue)
- newCachedThreadPool:实际执行ThreadPoolExecutor类,而这个调用的ThreadPoolExecutor的工作队列(SynchronousQueue)
- newScheduledThreadPool:最大的不同是执行了ScheduledThreadPoolExecutor,而这个调用的ThreadPoolExecutor的工作队列**(DelayedWorkQueue)**
Note:这里面指的fix,signle和cached都是指定的是corePoolSize(空闲的时候也不会收回)和maximumPoolSize(最大线程数);最大的问题是使用的工作队列是LinkedBlockingQueue,理论上是无限长的,在服务端运行的时候有可能出现(oom的故障);这个在后面会解决
newFixedThreadPool | newSingleThreadExecutor | newCachedThreadPool | newScheduledThreadPool | |
---|---|---|---|---|
线程长期维护数 | 自定义大小 | 1 | 0 | 自定义大小 |
最大的线程数 | 自定义大小 | 1 | Integer.MAX_VALUE | Integer.MAX_VALUE |
使用的链表 | LinkedBlockingQueue | LinkedBlockingQueue | SynchronousQueue | DelayedWorkQueue |
推荐的模式
Executors实际调用的也是ThreadPoolExecutor类,他会发生一些oom的问题是因为他用的工作队列是无限扩张的,在长期运行的服务器中可能会因此出现线程耗尽问题。
介绍一些ThreadPoolExecutor的构造方法
ThreadPoolExecutor(int corePoolSize, 线程池,就算内容是空的也会一直占有资源,不会释放
int maximumPoolSize, 线程上限
long keepAliveTime, 系统回收线程的时间
TimeUnit unit, 时间的单位
BlockingQueue<Runnable> workQueue, 工作队列(最主要的问题所在)
ThreadFactory threadFactory, 线程工厂,默认会赋值DefaultThreadFactory
RejectedExecutionHandler handler) 拒绝策略
线程池工作策略
If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
翻译:
- 小于核心线程,新线程增加
- 核心线程和更多线程再跑,会排队
- 如果一个请求不能排队了,一个新线程会创建。如果草果了最大的线程数,那么任务就会被拒绝。
工作流程:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略
推荐的创建模式:
ExecutorService executorService = new ThreadPoolExecutor(2, 2,
0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM
new ThreadPoolExecutor.DiscardPolicy()); //拒绝模式
运行
ExecutorService一共有三种运行方法,具体区别如下
方法 | 是否关心返回结果 |
---|---|
Future submit(Runnable task, T result) | 否,虽然返回Future对象,但是总是null |
Future submit(Callable task) | 是,通过get方法去获取 |
void execute(Runnable command) | 否 |
拒绝模式
Executors默认创建的拒绝模式是一个DiscardPolicy
一共有4种拒绝的方式:
方式 | 详细的解释 |
---|---|
AbortPolicy | 抛出一个RejectedExecutionException的错误,然后不做任何处理 |
DiscardPolicy | 啥也不做,不抛错,也不做啥 |
DiscardOldestPolicy | 线程如果没有关系,那么会把这个错误的Queue移除 |
CallerRunsPolicy | 会判断多线程有没有关闭,没有的话继续执行一遍 |
工作队列
工作队列有很多种:
这个取决的就是排列的方式,和执行顺序;主要调用的是它的offer()方法
- ArrayBlockingQueue:顺序执行
- BlockingDeque:
- DelayQueue:
- LinkedBlockingDeque:
- LinkedTransferQueue:
- PriorityBlockingQueue:
- DelayedWorkQueue:延时队列(newScheduledThreadPool的队列)
- SynchronusQueue:同步队列
- TransferQueue:
这个有点多,暂时先这样,具体队列使用的环境也有点嗯。。。不了解
使用的场景
暂时问了一些阿里的童鞋:他们说一些异步场景的时候很有用,就丢给队列然后慢慢处理。
具体的场景由于我的工作经验也比较有限,所以暂时不能列举出一些很好的例子。
使用过程中问题
- executors不使用shutdown不会退出程序:这个是在等待新的任务进来。
- executors使用的过程中测试抛错,发现任务执行不下去:这是由于错误抛错到了main程序了,所以导致任务进行不下去。
- Note:newScheduledThreadPool的执行方法,如果是submit的话也是直接运行的;有几个定时的方法schedule(),scheduleAtFixedRate(),scheduleWithFixedDelay();这个名字已经很明显了,就不解释了。’