线程是个吃资源大户,如果没有很好地管理线程,容易造成许多问题,所以线程池应运而生
-
为什么要使用线程池
1. 降低系统资源损耗,线程的创建、运行、销毁都需要消耗系统资源,通过线程池可以达到线程的复用,避免无用的消耗
2. 提高响应速度,任务到达时可以直接使用线程,不需要再等待线程的创建
3. 提高线程的可管理性
-
线程池执行逻辑
逻辑顺序:核心线程——>阻塞队列——>最大线程数——>拒绝策略
-
怎么使用线程池
简单的方法就是通过使用Executors提供的方法来创建,例如:
newFixedThreadPool(),创建固定大小的线程池
newSingleThreadExecutor():创建只有一个线程的线程池
newCachedThreadPool():创建无上限的线程池
但是快捷的方法大部分时间并不能满足于实际业务需求,在平时使用时,普遍采用调用ThreadPoolExecutor的构造方法来进行线程池的创建,即
几个参数的定义分别是:
-
corePoolSize:核心线程池的大小,里面的线程不会被回收。如果核心线程池没满,就会创建新的线程,满了的话就会存储到阻塞队列里
-
maximumPoolSize:线程数的上限。当阻塞队列已满时,并且当前线程池线程个数没有超过maximumPoolSize的话,就会创建新的线程来执行任务
-
keepAliveTime:空闲线程存活时间。如果线程线程池满了,就会将超过keepAliveTime的空闲的线程清除
-
unit:keepAliveTime的时间单位
-
workQueue:用来保存任务的阻塞队列
-
threadFactory:线程创建工厂
-
handler:拒绝策略,线程池无法容纳更多的线程来处理任务,那么这些任务就会被拒绝,拒绝的策略如下
- AbortPolicy: 拒绝任务,并抛出RejectedExecutionException异常
- CallerRunsPolicy:只用调用者所在的线程来执行任务
- DiscardPolicy:不做处理,直接忽略
- DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务
来实际测试一下
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorTest {
public static void main(String[] args) {
//不使用线程池
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new runnable1());
thread.start();
}
//使用线程池后,可以进行线程复用
ThreadPoolExecutor executorService = new ThreadPoolExecutor(
2, 10, 5, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(3),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
executorService.execute(new runnable1());
}
}
static class runnable1 implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程被调用了");
}
}
}
输出结果为
上面是使用execute()方法来提交任务,主要是针对不需要返回值的任务,所以无法知道任务是否执行成功
还可以使用submit()方法,这个方法会返回一个Future对象,通过这个对象来判断任务是否执行成功
Future future = executorService.submit(new runnable1());
使用Future类的get方法可以获取返回值,get()方法会进行阻塞,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
关闭线程池可以通过调用线程池的shutdown或shutdownNow方法,前者不会关闭暂停正在执行的线程,后者则是关闭所有线程,使用情况据实际场景而定
executorService.shutdown();
executorService.shutdownNow();
-
总结
1. 避免使用Executor的快捷方法,防止因为无界队列出现OOM或因为线程数设置不当导致线程数耗尽的情况
2. submit方法也是调用了execute方法,不过多加了Future类的返回值
3. 参数阻塞队列需要设置界限,使用无界队列会造成任务一直积压大量使用内存导致系统崩溃