传统艺能,点到为止,如果我都明白的话,我也不在这里了。
在平时的项目中经常创建、启动和销毁一个线程都是非常消耗时的,这是因为多线程的运行状态有新建,就绪,运行,休眠,死亡, 新建出一个线程并不是马上执行,而是转变为就绪状态等待cpu调度,运行完成到销毁也是一段时间的。于是就有人提出了线程池来复用线程,也就是新建、就绪、运行、运行完后不会立刻销毁掉,而是给下一个线程进行复用,不用再次经历新建和就绪的状态。
是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。
线程池的核心是ThreadPoolExecutor类,但是大多情况下使用Executors类中提供好的四种线程池创建函数,Executors封装了ThreadPoolExecutor,这四种线程池的创建函数一定要记住,有下面这四种:
遵循两个原则CPU密集和IO密集
什么是cpu密集和IO密集?
CPU密集:用任务管理器可以查看CPU的有核心数,在线程调度频繁的情况下让线程数和CPU数相同
IO密集:操作数据库,IO需要等待、阻塞,CPU需要等待很长的时间,配置成2*CPU核心数。
使用并发包中的Executor类的静态函数newCachedThreadPool,其实它还是用的ThreadPoolExecutor类,也就是说我们也可以用ThreadPoolExecutor自定义一个线程池。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
用法还是so easy的
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + temp);
}
});
}
}
}
使用线程池可以复用以前的线程,而不去创建新的线程
newFixedThreadPool用例
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + temp);
}
});
}
}
newScheduledThreadPool用例
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
int temp = i;
// 用schedule,设定定时任务,3秒后启动
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + temp);
}
},3, TimeUnit.SECONDS);
}
}
newSingleThreadExecutor用例
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
int temp = i;
// 唯一线程执行任务
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + temp);
}
});
}
}
疑问点①,为什么这里的temp不用final也能在run方法里面用呢?
是因为jdk1.7/8之后的优化,temp定后,不做修改,声明的变量定义只出现一次,都默认作为final类型
疑问点②,为什么主线程不能停止?如果要停止应该怎么修改代码?
线程池没有shutdown,使用shutdown()方法,shutdown阻塞等待所有正在调度的方法执行完毕后在主线程结束掉线程池
修改后的代码:
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
int temp = i;
// 唯一线程执行任务
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + ": " + temp);
}
});
}
executorService.shutdown();
}
核心是使用ThreadPoolExecutor类,下面是单线程池创建
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
所以主要是弄清ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
int corePoolSize, 核心池大小,
int maximumPoolSize, 最大池大小,正在线程池大小
long keepAliveTime, 存活时间,终止时间,超时秒数
TimeUnit unit,时间单位
BlockingQueue
线程池中有两个池子,一个核心线程池,另一个是最大线程池,用户提交的线程给线程池,不会立马到最大线程池中去,而是核心线程池中,核心线程池可以理解为初始化的池子,其大小被初始化,缓存线程池CachedThreadPool核心池大小为0,最大线程池表示最大容量是多少,newSingleThreadExecutor中,其corePoolSize和maximumPoolSize都是1,那么就是只有单线程执行任务。
keepAliveTime 和unit
keepAliveTime当线程数大于核心数时,这是多余的空闲线程在终止前等待新任务的最长时间
unit为keepAliveTime的时间单位,如毫秒,秒等
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {
@code keepAliveTime} argument
假定核心线程池大小为5,如果池子里线程超出了5个之后,会将用户提交的任务放到线程缓存队列中,如缓存队列满了到最大线程池中去执行,如果核心线程池有空闲,缓存队列中的任务则到核心线程池去运行,如果最大线程池也满了,会进行拒绝任务策略。
第五个参数阻塞队列BlockingQueue
阻塞队列可以设定超时时间
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);
}
源码的注释都已经写的很清楚了。
分3个步骤进行。
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);