回顾线程池技术------学习笔记
一、出现的背景
Thread功能繁多,而且对线程数量没有管控,对于线程的开辟和销毁要消耗大量的资源。每次new一个Thread都要重新开辟内存
二、优点或者功能
1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
3、提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
三、了解有关的几个类
首先是Executor:
public interface Executor {
void execute(Runnable command);
}
是一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类
ExecutorService:
public interface ExecutorService extends Executor{
}
是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,返回 Future 对象,以及可跟踪一个或多个异步任务执行状况返回Future的方法;可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。
Executors类: 主要用于提供线程池相关的操作
使用 Executors 创建四种类型的线程池
- FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
它是一种固定大小的线程池;
corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;
由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
2. CachedThreadPool
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}
- SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
它只会创建一条工作线程处理任务;
采用的阻塞队列为LinkedBlockingQueue;
4. ScheduledThreadPool
它用来处理延时任务或定时任务。
Executors为我们提供了这四种线程池。我们可以使用这么这几个线程解决日常问题。但在使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出)。
四、以自定义创建线程池
除了上述的四种Executors提供的四种线程池外。我们当然可以自定义创建线程池来更加效率的满足我们的需求。
直接调用ThreadPoolExecutor的构造函数(还有很多个不同参的构造函数)来自己创建线程池,上面工具类的四个线程池也是直接或间接如此创建的。源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
corePoolSize :线程池中核心线程数的最大值
maximumPoolSize :线程池中能拥有最多线程数
keepAliveTime :表示空闲线程的存活时间。
TimeUnit unit :表示keepAliveTime的单位。
threadFactory :指定创建线程的工厂
handler :拒绝策略
详细见一位前辈的博客非常详细,参数的关系和差异讲的也很生动ThreadPoolExecutor 参数详解
五、任务的提交过程
1.submit方法
submit 的实现方法位于抽象类 AbstractExecutorService 中,而此时 execute 方法还未实现(而是在 AbstractExecutorService 的继承类 ThreadPoolExecutor 中实现)。submit 有三种重载方法,这里我选取了两个常用的进行分析,可以看出无论哪个submit 方法都最终调用了 execute 方法
2.execute方法
由于execute方法中多次调用 addWorker ,我们这里就简要介绍一下它,这个方法的主要作用就是创建一个线程来执行Runnnable对象。
3.addWorker方法
六、线程池代码实例:
import java.util.concurrent.*;
public class ThreadPool {
/**
* 创建线程池
* @return 返回线程池管理对象实例
*/
public ThreadPoolExecutor createThreadPool(){
return new ThreadPoolExecutor(10, 15,
20L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10),
//拒绝策略:抛弃最旧的任务(最先提交而没有得到执行的任务)
new ThreadPoolExecutor.DiscardOldestPolicy());
}
}
import java.util.Random;
public class Task implements Runnable{
private int id;
public Task(int id){
this.id = id;
}
@Override
public void run() {
int t =new Random().nextInt(10);
/* for (int i = 0; i < t; i++) {
System.out.println(Thread.currentThread().getName() + "run : " + i);
}*/
try {
Thread.sleep(t*500);
}catch (Exception e){
e.printStackTrace();
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
public class ThreadPoolTest {
@Test
public void threadPoolTest(){
ThreadPool threadPool = new ThreadPool();
ThreadPoolExecutor threadPoolExecutor =threadPool.createThreadPool();
for (int i = 0; i < 20; i++) {
Task task = new Task(i);
//每0.5s提交一个任务
try {
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
threadPoolExecutor.execute(task);
}
}
}