线程使用上的问题
- 线程的频繁创建好销毁
- 线程的数量过多,会造成CPU资源的开销。
- 上下文切换 (消耗CPU资源)。
线程的复用
连接池、对象池、内存池、线程池 。
- 池化技术的核心: 复用
线程池
- 提前创建一系列的线程,保存在这个线程池中。
- 有任务要执行的时候,从线程池中取出线程来执行。
- 没有任务的时候,线程池放回去。
Excutors类中创建线程的方法(不建议使用这种,使用ThreadPoolExecutor最好,规避资源浪费)
- newFixedThreadPool 固定线程数量,任务队列采用的是LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- newSingleThreadExecutor 只有一个线程的线程池,任务队列采用的是LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- newCachedThreadPool 可以缓存的线程池 ->理论上来说,有多少请求,该线程池就可以创建多
少的线程来处理。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newScheduledThreadPool //提供了按照周期执行的线程池. ->Timer
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
使用小Demo
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(4);
for(int i=0;i<10;i++){
//把一个实现了Runnable接口的任务交给线程池执行
executorService.execute(new Task());
}
executorService.shutdown();
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-开始执行任务");
try {
Thread.sleep(new Random().nextInt(1000));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
执行任务的两种方式
execute() 和 submit() 都是用来执行线程池任务的,它们最主要的区别是,submit() 方法可以接收线程池执行的返回值,而 execute() 不能接收返回值。
submit和Callable以及Future有关
ThreadPoolExecutor
猜想参数
- 线程池的实现原理的过程推演
- 需求: 实现线程的重复使用.
- 分解:
如何线程的复用.
让线程实现复用的唯一的方法,就是让线程不结束。 - 那如何让线程执行新的任务呢?也就是说,任务怎么给他执行?
线程传递参数通过[共享内存],比如把任务放在一个集合里面,线程自己去取 - 线程一直处于运行状态,合理吗?
- 有任务来的时候,执行
- 没有任务的时候,阻塞
结论: 通过阻塞队列的方式,来实现线程池中线程的复用。
有这些猜想可以自己写个线程池中线程的简易版代码!
public class ThreadExample implements Runnable {
private static BlockingDeque<Object> tasks=new LinkedBlockingDeque<>();
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
Object take = tasks.take();//阻塞方法,有任务返回一个Object,无任务阻塞
((Runnable)take).run();//这个地方就应该调用run方法,它本身就是线程在执行
}
}
public static void main(String[] args) {
tasks.add(new Task());
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println("干活");
}
}
}
猜想总结图
源码验证
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;//核心线程数
this.maximumPoolSize = maximumPoolSize;//最大线程数,不使用后要让它们结束
this.workQueue = workQueue;//阻塞队列(任务队列)
this.keepAliveTime = unit.toNanos(keepAliveTime);//时间
this.threadFactory = threadFactory;//线程工厂,用来创建工作线程的,可以不传这个参数,有默认实现
//如果想自定义线程的名字那么就需要穿这个参数
this.handler = handler;//拒绝策略,可以不传这个参数,有默认实现
}
execute方法
- 先初始化核心线程。
- 调用阻塞队列的方法,把task存进去。(offer() -> true/false)
- 如果true ,说明当前的请求量不大, 核心线程就可以搞定。
- false,增加工作线程(非核心线程)
- 如果添加失败,说明当前的工作线程数量达到了最大的线程数,直接调用拒绝策略。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判断当前工作线程数是否小于核心线程数(延迟初始化)
//注意这个ctl是AtomicInteger类型
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//添加工作线程的同时,执行command
return;
c = ctl.get();
}
//workQueue.offer 添加到阻塞队列
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);//拒绝策略
}
如何关闭线程池
关闭线程池
线程池关闭有两种方法即shutdown或shutdownNow方法。原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法被终止。
区别
-
shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表
-
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
-
调用任意一个方法,isShutdown方法就会返回true,所有任务都关闭后,才表示线程池关闭成功,这时调用isTerminaed返回true。
-
通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。
线程数量(千古难题)
IO密集型 CPU 2core+1(CPU利用率不高)
CPU密集型 CPU +1(CPU利用率很高,会增加上下文切换)