为什么需要线程池呢
java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作.
1:需要为线程堆栈分配和初始化大量的内存块,其中至少1MB栈内存.
2:需要进行系统调用,以便在OS(操作系统)中创建和注册本地线程.
如果频繁的创建和销毁线程会带来性能上的消耗,并且不被编程规范允许.所以线程池主要解决问题如下:
1:提升性能:线程池可以独立的负责线程的创建和维护分配,在执行大量异步任务的时候,不需要手动创建线程,线程池会进行调度,最大限度对已创建的线程复用.
2:线程管理:每个java线程池会保持一些线程池的基本统计信息.执行任务个数,时间等.可以对线程高效的管理,可以对异步任务高效调度.
JUC线程池架构
Executor:
提供了execute接口来执行提交的Runnable执行目标实例.作为执行者的角色,其目的是任务的提交者与任务执行者分离开的.
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
ExecutorService:
继承Executor,是异步目标任务的执行者服务接口,对外提供异步任务的接收服务,提供了接收异步任务并转交给执行者的方法.
//提交单个任务.
<T> Future<T> submit(Callable<T> task);
//批量提交任务.
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
AbstractExecutorService:
这是一个抽象类,是对ExecutorService方法的默认实现.
ThreadPoolExecutor:
继承于AbstractExecutorService抽象类.是一个线程池的实现类.为每个线程提供了和维护了一些基础的数据统计,方便线程的管理和监控.
ScheduledExecutorService:
是一个接口继承于ExecutorService,是一个可以完成延时和周期性任务的线程调度接口,功能和Timer/TimerTask类似.
ScheduledThreadPoolExecutor:
继承于ThreadPoolExecutor并且实现了ScheduledExecutorService接口,提供了延时和周期性任务的默认实现.
Executors创建线程的方式:
Executors.newSingleThreadExecutor创建单例化线程池:
该快捷方式创建一个单例化线程池,也就是一个线程来执行所有的任务.
public class SingleThreadPoolTest {
public static final int SLEEP_GAP = 500;
static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
private String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "任务:" + taskName + "doing");
//线程睡眠一会.
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ taskName + "运行结束");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService singleThreadExecutor =
Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
singleThreadExecutor.execute(new TargetTask());
singleThreadExecutor.execute(new TargetTask());
}
Thread.sleep(SLEEP_GAP);
singleThreadExecutor.shutdown();
}
}
从这个例子可以看出这个线程池的特点.
1:单线程化的线程池中的任务,是按照提交的次序执行的.
2:池中的线程存活时间是无限的.
3:当池中的唯一线程繁忙时,新提交的任务会进入阻塞队列,并且阻塞队列是无界的.
Executors.newFixedThreadPool()创建固定数量的线程池:
该快捷方式创建一个固定数量的线程池,唯一的参数就是用来设置线程数的大小.
public class FixedThreadPoolTest {
public static final int SLEEP_GAP = 500;
static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
private String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "任务:" + taskName + "doing");
//线程睡眠一会.
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ taskName + "运行结束");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService singleThreadExecutor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
singleThreadExecutor.execute(new SingleThreadPoolTest.TargetTask());
singleThreadExecutor.submit(new SingleThreadPoolTest.TargetTask());
}
Thread.sleep(SLEEP_GAP);
singleThreadExecutor.shutdown();
}
}
从这个例子可以看出这个线程池的特点.
1:如果线程数没有达到固的数量,每提交一个任务就会创建一个线程,直到达到固的数量.
2:线程一旦达到固的数量就会保持不变,如果某个线程因为异常而结束,那么线程池会在补充一个线程.
3:当池中所有线程繁忙时,新提交的任务会进入阻塞队列,并且阻塞队列是无界的.
适用场景:
需要任务长期执行的场景,固的线程池的线程数可以稳定在一个一个数.避免了频繁的回收和创建,所以适用于CPU密集型的任务.在CPU长期使用的情况下,减少线程的切换.
缺点:
内部使用无界队列来放任务,当大量任务需要处理时,队列无限增大,使资源迅速耗尽.
Executors.newCachedThreadPool创建可缓存线程池
该快捷方式创建一个创建可缓存线程池,如果池内的某些线程无事可干成为空闲线程,可灵活回收.
public class CachedThreadPoolTest {
public static final int SLEEP_GAP = 500;
static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
private String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "任务:" + taskName + "doing");
//线程睡眠一会.
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ taskName + "运行结束");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
singleThreadExecutor.execute(new SingleThreadPoolTest.TargetTask());
singleThreadExecutor.submit(new SingleThreadPoolTest.TargetTask());
}
Thread.sleep(SLEEP_GAP);
singleThreadExecutor.shutdown();
}
}
从这个例子可以看出这个线程池的特点.
1:在接收新的异步任务target执行目标实例时,如果线程池内所有线程繁忙,就会创建新的线程处理任务.
2:不会限制线程池的大小,线程大小完全依赖于操作系统(或者JVM)所能创建的线程大小.
3:如果部分线程空闲,存活的线程大于任务数量(默认为60s)就会被回收.
适用场景:
快速处理突发性强,耗时较短的任务场景,如netty的NIO处理场景Rest API接口的瞬时削峰场景.可缓存线程池的线程数量不固定,只要有空闲线程就会被回收.接收到新的异步任务,没有可用线程就直接创建一个.
缺点:
线程池没有最大数量的限制,如果大量异步任务执行目标实例同时提交,会因为线程过多而导致资源耗尽.
Executors.newScheduledThreadPool创建一个定时线程池
该快捷方式创建一个可以调度线程池,提供一个延时和周期性任务的线程池
public class ScheduledThreadPoolTest {
public static final int SLEEP_GAP = 500;
static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
private String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "任务:" + taskName + "doing");
//线程睡眠一会.
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ taskName + "运行结束");
}
}
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService scheduledExecutorPool =
Executors.newScheduledThreadPool(2);
for (int i = 0; i < 2; i++) {
//0代表首次执行任务的延迟时间.500代表每次任务的间隔时间.
scheduledExecutorPool.scheduleAtFixedRate(new TargetTask(),
0,500, TimeUnit.MILLISECONDS);
}
Thread.sleep(SLEEP_GAP);
// scheduledExecutorPool.shutdown();
}
}
.适用场景:
周期性执行的任务.
public ScheduledFuture<?> scheduleAtFixedRate(
//异步任务
Runnable command,
//首次执行延时
long initialDelay,
//两次开始执行最小间隔时间
long period,
//时间计时单位
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(
//执行任务实例.
Runnable command,
//首次执行延时.
long initialDelay,
//两次执行最小间隔时间.
long delay,
//时间单位
TimeUnit unit);
线程池的标准创建方式:
ThreadPoolExecutor构造方法有很多重载版本,其中比较重要的的构造器如下:
public ThreadPoolExecutor(
//核心线程数,即使空闲也不会回收.
int corePoolSize,
//线程数的上限
int maximumPoolSize,
//最大空闲时间.
long keepAliveTime,
//存活时间单位
TimeUnit unit,
//阻塞队列
ThreadFactory threadFactory,
//任务拒绝策略
RejectedExecutionHandler handler)
1:核心和最大线程数量
corePoolSize参数用于设置核心线程数量,maximumPoolSize用于设置最大线程数量,线程池会根据这两个参数自动维护线程池中的工作线程,大致规则为:
1:当线程池接收到新的任务,并且当前工作线程少于核心线程数时.即使其他工作线程处于空闲状态,也会创建一个线程来处理新的请求,直到线程数达到核心线程数.
2:如果当前工作线程数大于核心线程数并且小于最大线程数,只有当队列任务满了,才会创建一个新的线程.通过设置核心线程数和最大线程数相等,可以创建一个固定大小的线程池.
3:当最大线程数设置为无界值.线程池可以接收任意数量的并发任务.
4:核心线程数和最大线程数不仅可以再构造方法中设置,还可以通过set方法设置.
public void setCorePoolSize(int corePoolSize){}
public void setMaximumPoolSize(int maximumPoolSize) {}
BlockingQueue:
BlockingQueue(阻塞队列)实例用于暂时接收异步任务,如果线程池的核心线程池都在忙,所接收的异步任务都会放在阻塞队列中.
keepAliveTime和TimeUnit unit:
空闲线程的存活时间和时间单位.
ThreadFactory threadFactory:
线程工厂,线程池通过这个工厂类创建线程.可以继承对应的接口,根据业务实现对应的线程.
RejectedExecutionHandler handler:
异步任务的拒绝策略,除了默认的实现以外,还可以通过实现接口RejectedExecutionHandler自定义拒绝策略.
线程提交任务的两种方式:
方式一:
Executor接口中的方法.
public void execute(Runnable command) {}
方式二:
ExecutorService接口中的方法.
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
这两种提交方式的区别:
1:二者接收的方法参数类型不一样
execute方法只能接受Runnable类型的参数.submit方法能够接受Runnable类型的参数也能接受Callable类型参数.Runnable类型的不可以返回结果,不允许抛出异常.Callable类型可以返回结果.允许抛出异常.
2:submit提交任务会有返回值,execute提交任务没有返回值,
execute主要用于任务的提交执行,任务执行的结果和异常调用者并不关心.submit方法也可以用于启动任务的执行,启动以后会返回一个Future对象.代表一个异步执行的实例,可以获取执行结果.
3:submit方法方便Exception处理.
execute方法在启动任务执行后,对执行过程中产生的异常并不关心.,通过submit方法提交可以通过返回的Future实例捕获异常.
通过submit()方法返回的Future对象获取结果
public class CreateThreadPoolDemo {
public static void main(String[] args) {
ScheduledExecutorService threadPool =
Executors.newScheduledThreadPool(2);
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Random random = new Random();
return random.nextInt(10);
}
});
try {
Integer integer = future.get();
System.out.println("异步执行的结果" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
threadPool.shutdown();
}
通过submit()方法返回的Future对象捕获异常
public class ErrorThreadPoolDemo {
public static final int SLEEP_GAP = 500;
static class TargetTask implements Runnable {
static AtomicInteger taskNo = new AtomicInteger(1);
private String taskName;
public TargetTask() {
taskName = "task-" + taskNo.get();
taskNo.incrementAndGet();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "任务:" + taskName + "doing");
//线程睡眠一会.
try {
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ taskName + "运行结束");
}
}
static class ErrorTask extends TargetTask {
@Override
public void run() {
super.run();
throw new RuntimeException("我抛出了异常.");
}
}
public static void main(String[] args) {
ScheduledExecutorService threadPool =
Executors.newScheduledThreadPool(2);
ErrorTask errorTask = new ErrorTask();
Future<?> future = threadPool.submit(errorTask);
try {
if (future.get() == null) {
System.out.println("任务完成");
}
} catch (Exception e) {
System.out.println(e.getCause().getMessage());
}
threadPool.shutdown();
}
}
坚持的意义在哪里呢,其实我自己的感觉就是,没有告诉你应该怎么做,所以我们只能通过学习不断地去摸索去探索,只要在路上,我们终会成功.
如果大家喜欢我的分享的话.可以关注一下微信公众号
心有九月星辰.