线程池的定义
线程池其实是一种多线程的形式,处理过程中可以将任务添加到队列中,然后创建线程后自动启动这些任务。
为什么使用线程池
使用线程池最大的原因就是可以根据系统的需求灵活控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统运行的效率,降低系统的运行压力。
使用线程池有哪些优势
- 线程和任务分离,提升线程的重用性;
- 控制线程并发数量,降低服务器压力,统一管理所有线程;
- 提升系统的响应速度,使用线程池节省了创建线程和销毁线程所用的时间。
线程池使用场景
-
商品秒杀
-
购票
只要有并发的地方,都可以使用线程池。
Java内置线程池原理剖析
public ThreadPoolExecutor(int corePoolSize, // 核心线程数量
int maximumPoolSize, // 最大线程数量
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
) {……}
-
corePoolSize(核心线程数)
核心线程数需要根据任务的处理时间和每秒产生的任务数量来确定。可以按照8020原则设计,80%的情况用核心线程数处理,20%的情况用最大线程数处理。
-
workQueue(任务队列)
一般设计为(核心线程数 /单个任务执行时间)* 2
-
maximumPoolSize(最大线程数)
根据每秒产生的最大任务数决定,最大线程数 = (最大任务数 - 任务队列长度)* 单个任务执行时间。
-
keepAliveTime(最大空闲时间)
没有固定值。
Java内置线程池--ExecutorService
获取线程池对象ExecutorService
-
每提交一个任务就创建一个线程
static ExecutorService newCachedThreadPool()
创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建。
static ExecutorService newCachedThreadPool(ThreadFactorythreadFactory)
线程池中所有的线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行。
-
创建固定数量的线程池
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建
-
整个线程池只有一个线程,任务需要排队来进行处理
static ExecutorService newSingleThreadExecutor()
创建一个使用单个worker线程的Executor,以无界队列方式运行该线程
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建
ExecutorService线程对象的方法
-
void shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务
-
List<Runnable> shutdownNow()
停止所有正在执行的线程,暂停处理正在等待的任务,并返回等待执行的任务列表
-
<T> Future<T> submit(Callable<T> task)
执行带返回值的任务,返回一个Future对象
-
Future<?> submit(Runnable task)
执行Runnable任务,并返回一个表示该任务的Future
-
<T> Future<T> submit(Runnable task, T result)
执行Runnable任务,并返回一个表示该任务的Future
Java内置线程池--ScheduledExecutorService
-
获取线程池对象ScheduledExecutorService
-
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟执行或定期执行任务
-
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务
-
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后执行或者定期地执行
-
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
使用ThreadFactory来创建一个单线程执行程序,它允许在给定延迟后执行或者定期地执行
-
-
ScheduledExecutorService线程对象的方法:
-
实现Callable接口的任务,只有延迟
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,delay的时间后执行callable
-
实现Runnable接口的任务,只有延迟
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,delay的时间后执行command
-
任务的工作时间算在延迟时间里面,延迟+重复执行
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period,TimeUnit unit)
创建并执行一个在给定初始延迟时间后启用的定期任务,在每一次执行开始和下一次执行开始之间都存在给定的延迟。
-
任务的工作时间不算在延迟时间里面,延迟+重复执行
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay, TimeUnit unit)
创建并执行一个在给定初始延迟时间后启用的定期任务,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
-
Java内置线程池-异步计算结果(Future)
Future 的常用方法如下:
-
boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行 -
V get()
等待计算完成,然后获取其结果 -
V get(long timeout, TimeUnit unit)
最多等待给定的时间之后获取其结果,给定时间内获取不到结果抛出异常 -
boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true -
boolean isDone()
如果任务已完成,则返回 true一般使任务类实现Callable接口,因为实现Runnable接口重写的run()方法没有返回值,而Callable接口的call()方法可以指定返回值。其返回值可以使用线程池对象的Future submit(Callable task)方法的返回值Future的get()方法得到,还可以对任务进行取消和是否完成等操作
案例
秒杀商品,20个人秒杀10个商品,要求10个秒杀成功,10个秒杀失败,使用线程池,注意线程安全问题
public class MyTest {
public static void main(String[] args) {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(15));
for (int i = 1; i <= 20; i++) {
MyTask task = new MyTask("客户" + i);
executor.submit(task);
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private static int id = 10;
private String userName;
public MyTask(String userName) {
this.userName = userName;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName + "正在使用" + name + "参与秒杀任务");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class) {
if (id > 0) {
System.out.println(userName + "使用" + name + "秒杀:" + id-- + "号商品成功");
} else {
System.out.println(userName + "使用" + name + "秒杀失败");
}
}
}
}
客户3正在使用pool-1-thread-3参与秒杀任务
客户2正在使用pool-1-thread-2参与秒杀任务
客户1正在使用pool-1-thread-1参与秒杀任务
客户19正在使用pool-1-thread-4参与秒杀任务
客户20正在使用pool-1-thread-5参与秒杀任务
客户1使用pool-1-thread-1秒杀:10号商品成功
客户3使用pool-1-thread-3秒杀:9号商品成功
客户2使用pool-1-thread-2秒杀:8号商品成功
客户4正在使用pool-1-thread-3参与秒杀任务
客户20使用pool-1-thread-5秒杀:7号商品成功
客户5正在使用pool-1-thread-5参与秒杀任务
客户19使用pool-1-thread-4秒杀:6号商品成功
客户6正在使用pool-1-thread-2参与秒杀任务
客户8正在使用pool-1-thread-4参与秒杀任务
客户7正在使用pool-1-thread-1参与秒杀任务
客户5使用pool-1-thread-5秒杀:5号商品成功
客户4使用pool-1-thread-3秒杀:4号商品成功
客户10正在使用pool-1-thread-3参与秒杀任务
客户7使用pool-1-thread-1秒杀:3号商品成功
客户8使用pool-1-thread-4秒杀:2号商品成功
客户9正在使用pool-1-thread-5参与秒杀任务
客户12正在使用pool-1-thread-4参与秒杀任务
客户6使用pool-1-thread-2秒杀:1号商品成功
客户11正在使用pool-1-thread-1参与秒杀任务
客户13正在使用pool-1-thread-2参与秒杀任务
客户10使用pool-1-thread-3秒杀失败
客户14正在使用pool-1-thread-3参与秒杀任务
客户9使用pool-1-thread-5秒杀失败
客户15正在使用pool-1-thread-5参与秒杀任务
客户12使用pool-1-thread-4秒杀失败
客户16正在使用pool-1-thread-4参与秒杀任务
客户13使用pool-1-thread-2秒杀失败
客户11使用pool-1-thread-1秒杀失败
客户17正在使用pool-1-thread-2参与秒杀任务
客户18正在使用pool-1-thread-1参与秒杀任务
客户15使用pool-1-thread-5秒杀失败
客户18使用pool-1-thread-1秒杀失败
客户16使用pool-1-thread-4秒杀失败
客户17使用pool-1-thread-2秒杀失败
客户14使用pool-1-thread-3秒杀失败
总结
- 利用Executors工厂类的静态方法,创建线程池对象
- 编写Runnable或Callable实现类的实例对象
- 利用ExecutorService的submit方法或ScheduledExecutorService的submit方法提交并执行线程任务
- 如果有执行结果,则处理异步执行结果(Future)
- 调用shutdown()方法,关闭线程池