开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
平时我们开发中,总离不开与线程打交道,经常会开启线程去做一些耗时任务,使用线程,便需要去对线程进行管理,下面我们一起来看看线程的创建和使用线程池对线程的管理。
正文
1. 线程的创建
首先我们来看看线程的创建方式:
1.1. 直接继承Thread类:
class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("thread run.");
}
}
使用的时候,直接创建线程并且直接start
public void startThread() {
MyThread myThread = new MyThread();
myThread.start();
}
1.2. 使用Runnable
public void runnable() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run.");
}
});
thread.start();
}
1.3. Callable+Future
这种方式比较特殊,但是我们可以通过Future的get方法获取线程执行结束后的返回值,需要留意的是get方法会阻塞线程。
public void callable() {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
int result = futureTask.get();
System.out.println("result:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
2. 线程池使用
平时我们开发的时候,会经常创建线程去做一些耗时操作,如果是直接就创建线程的话,会存在无法统一管理的问题,甚至因为无限创建线程而导致占有过多的系统资源和OOM发生。
创建线程池:
ExecutorService executorService = new ThreadPoolExecutor();
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:表示核心线程的数量,默认情况下,核心线程会一直存活,即使处于闲置状态,如果我们将其allowCoreThreadTimeOut属性设置为true的话,那么核心线程就会超时策略,如果达到keepAliveTime这个时间,核心线程便会被回收。
- maximumPoolSize:线程池中能够容纳的最大线程数量,包括核心线程与非核心线程
- keepAliveTime:非核心线程如果闲置时长超过这个时长,那么将被回收,如果设置了allowCoreThreadTimeOut属性为true时,也会对核心线程生效。
- unit:keepAliveTime的单位,是一个枚举类型。
- workQueue:阻塞队列,用于存放等待执行的任务。
- threadFactory:为线程池提供创建线程的工厂类,,默认为DefaultThreadFactory类。
- handler:当任务队列已经满了并且活动的线程数已达到了上限,那么这个时候就会调用RejectedExecutionHandler的rejectedExecution方法,默认使用的是AbortPolicy,它里边的实现是抛出一个RejectedExecutionException异常。
提交任务:
提交任务有两种方式,第一种是调用execute:
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("execute");
}
});
第二种是submit:
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
});
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
两者的区别是:submit会返回一个Future,我们可以通过Future的get方法来获取执行结果,当然如果任务还没执行完的时候,get方法会导致阻塞。
线程池的关闭:
- shutdown():将线程池状态设置为SHUTDOWN,中断没有正在执行任务的线程。
- shutdownNow():将线程池的状态设置为STOP,中断所有线程,并且返回等待执行任务的列表。当需要立即中断所有的线程,不一定需要执行完任务,那么可以直接调用此方法。
3. 线程池执行原理
往线程池提交任务后,会执行下面的步骤:
- 如果核心线程池中的线程数量还没达到核心线程数上限时,这个时候会启动一个核心线程来执行任务。
- 如果已经达到核心线程数上限,这个时候将会放入任务队列,等待执行。
- 如果任务队列已满,这个时候线程池中的线程数没有达到设定的线程数上限时,则会启动一个非核心线程来执行任务。
- 如果线程池中的线程数已达到设定的线程数上限,那么就不会执行此任务,就会调用RejectedExecutionHandler的rejectedExecution方法。
4. 线程池分类
在Java中已经替我们封装好四种不同种类的线程池了,分别有:newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。
- newFixedThreadPool:固定核心数的线程池,这个创建的线程池的线程数量上限是核心线程的数量多上限,也就是只有核心线程,如果所有线程都在执行任务时,新提交的任务则会处于等待状态。
Executors.newFixedThreadPool(2);
- newCachedThreadPool:核心线程数为0,非核心线程数可以说无限大(Integer.MAX_VALUE)个,如果线程池中的所有线程都在执行任务,则会创建新的线程。这些线程空闲后,等待60秒后会被回收。它的阻塞队列为SynchronousQueue,它内部是一个空的集合,也就是说新任务一来,不会放到任务队列里边,而是会马上得到执行。
Executors.newCachedThreadPool();
- newScheduledThreadPool:有固定的核心线程,但是没有限制非核心线程数,一旦非核心线程空闲了,就会被回收。同时也可以用它来做一些延迟或者周期性的任务。
Executors.newScheduledThreadPool(2);
- newSingleThreadExecutor:这个线程池里边只有一个核心线程,任务队列没有限制长度。
Executors.newSingleThreadExecutor();
结语
本次的分享到这里就结束了,主要分享了线程的创建,线程池的使用及管理,希望对大家有所帮助。天气变得很冷了,大家多多保暖的同时,也要记得做好防护哦。