前言
多线程编程是一个难点,好多面试官都会问。虽然平时用到多线程编程的地方比较少,掌握基本的用法事半功倍。
合理使用多线程的好处
1.降低资源消耗
2.提高响应速度
3.提高线程的可管理性
频繁的创建的Thread 类比较消耗系统资源,比较好的做法就是使用线程池来管理线程。
线程池的实现原理
当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?
1.判断核心线程池是否已满,未满则创建新线程,如果已满则执行下个步骤
2.线程池判断工作队列是否已满,未满则将任务存储在队列里,否则执行下个步骤
3.线程池中普通线程是否已满,未满则创建线程执行任务,否则交给饱和策略处理这个任务。
ThreadPoolExecutor 执行execute方法分四步:
1.如果当前运行的线程少于corePoolSize,则创建新线程来执行任务【执行需要获取全局锁】
2.如果执行的线程大于等于corePoolSize,则将当前线程放入Blockingqueue。
3.如果无法将任务加入BlockingQueue中,则创建新线程来执行任务【执行需要获取全局锁】
4.如果创建的新线程使当前运行的线程总数超过maxnumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
源码展示:
ThreadPoolExecutor.execute(Runnable command) 类
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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);
}
tips:这里引入一个Worker对象,该对象是线程池创建时,将Thread 封装成worker工作线程。
而,工作线程会循环获取BlockingQueue中的线程来执行。
线程池的使用
1.创建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
参数依次代表的意思:
1.定义核心线程数
2线程池最大线程数量
3.空闲线程存货的时间
4.时间单位
5.线程池缓冲队列,
6自定义线程池创建工厂
7.线程池饱和后的处理策略
tips:实现阻塞队列的几个类分别是
ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO对入队元素进行排序
LinkedBlockingQueue:基于链表数据结构的阻塞队列,吞吐率高于ArrayBlockingQueue队列。
SynchronousQueue: 一个不存储元素的阻塞队列,插入必须等待着另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量高于LinkedBlockingQueue
priorityBlockingQueue:一个具有优先级的无线阻塞队列
向线程池提交任务
接受实现了两种Runnable,Callable结构方式类 【Callable有返回值】,这里返回Future对象可以获取线程执行状况信息,线程是否执行成功。
tips:
new Future().get 会阻塞当前线程知道任务完成有返回值为止。
关闭线程池
ThreadPoolExecutor 类提供两种关闭方式
1.shutdown()
2.shutdownNow()
区别:
shutdown,shutdownNow,都是遍历线程池中的工作线程,然后调用线程的Interrupt方法来中断的。
shutdown将线程状态置成SHUTDOWN 然后中断所有没有正在执行的任务。
shutdownNow 将线程状态置成Stop,然后尝试停止所有正在执行或者暂停任务的线程并返回等待执行任务的列表。
只要调用了这两个方法中的一个isShutDown的方法就会返回True,当所有任务全部关闭后isTerminaed才会True。
合理地配置线程池的原则
任务性质:cpu密集型,还是i/o密集型和混合任务
任务的优先级:高中低
任务的执行时间:长短中
任务的依赖性:是否依赖其他资源
cpu密集型,应该少分配线程,cpu+1个。i/o密集型多分配线程2*cpu数量。线程池优先使用有界线程池
线程池的监控
如果系统中大量使用线程池,有必要对线程池进行监控,方便在出现问题是,对使用状况快速定位。
常用的几个api
beforeExecute,afterExecute 和 terninated ,任务执行前,执行后和线程池关闭前执行一些代码来进行监控
总结:
工作中线程池的使用避不开,尽早理解线程池的原理以及使用注意点对今后工作起到好的作用。合理配置线程池,对线程池进行监控避免线程池配置错误和有个错误可以快速定位。