什么是线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)
线程池的优势
- 线程和任务分离,提升线程重用性
- 控制线程并发数量,降低服务器压力,统一管理所有线程
- 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间
应用场景
- 网购商品秒杀
- 云盘文件上传和下载
- 12306网上购票系统等
ThreadPoolExecutor
ThreadPoolExecutor源码中构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量int maximumPoolSize,// 最大线程数long keepAliveTime, // 最大空闲时间TimeUnit unit, // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory, // 线程工厂RejectedExecutionHandler handler // 饱和处理机制)
{ ... }
线程池工作流程图
线程池参数设置原则
核心线程数量:核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;
任务队列:任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
最大线程数:最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
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。
ExecutorService获取
获取ExecutorService可以利用JDK中的Executors 类中的静态方法
static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 线程池中的所有线程都使用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来创建。
shutdown()和 shutdownNow()的区别
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
List<Runnable> shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表
submit()和execute()的区别
submit:参数可以是Runnable或Callable,有返回值Future
execute:参数是Runnable,无返回值
ScheduledExecutorService
ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务;
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
Future异步计算结果
常用方法
boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。 V get()
如有必要,等待计算完成,然后获取其结果。 V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。 boolean isDone()
如果任务已完成,则返回 true。
package cn.ting.demo04;import java.util.concurrent.*;/*练习异步计算结果*/
public class FutureDemo {public static void main(String[] args) throws Exception {//1:获取线程池对象ExecutorService es = Executors.newCachedThreadPool();//2:创建Callable类型的任务对象Future<Integer> f = es.submit(new MyCall(1, 1));//3:判断任务是否已经完成test1(f);
// boolean b = f.cancel(true);//System.out.println("取消任务执行的结果:"+b);//Integer v = f.get(1, TimeUnit.SECONDS);//由于等待时间过短,任务来不及执行完成,会报异常//System.out.println("任务执行的结果是:"+v);}//正常测试流程private static void test1(Future<Integer> f) throws InterruptedException, ExecutionException {boolean done = f.isDone();System.out.println("第一次判断任务是否完成:"+done);boolean cancelled = f.isCancelled();System.out.println("第一次判断任务是否取消:"+cancelled);Integer v = f.get();//一直等待任务的执行,直到完成为止System.out.println("任务执行的结果是:"+v);boolean done2 = f.isDone();System.out.println("第二次判断任务是否完成:"+done2);boolean cancelled2 = f.isCancelled();System.out.println("第二次判断任务是否取消:"+cancelled2);}
}
class MyCall implements Callable<Integer>{private int a;private int b;//通过构造方法传递两个参数public MyCall(int a, int b) {this.a = a;this.b = b;}public Integer call() throws Exception {String name = Thread.currentThread().getName();System.out.println(name+"准备开始计算...");Thread.sleep(2000);System.out.println(name+"计算完成...");return a+b;}
}
案列一:商品秒杀
案例介绍
假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢,假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败;
案例要求
使用线程池创建线
解决线程安全问题
package cn.ting.demo05;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ShopSekill {public static void main(String[] args) {seckill();}public static void seckill(){ExecutorService executorService = Executors.newFixedThreadPool(10);//20个人参加秒杀for (int i = 1; i <= 20; i++) {executorService.submit(new MyTask("用户"+i));}executorService.shutdown();}
}
class MyTask implements Runnable{//十部手机private static int i=10;//20个人private String username;public MyTask(String username) {this.username = username;}public void run() {String name = Thread.currentThread().getName();
// System.out.println(username+"开始使用"+name+"秒杀商品啦~~~~~~");synchronized(ShopSekill.class){if (i>0) {System.out.println(username+"开始使用"+name+"秒杀商品"+i--+"成功了!!");}else {System.out.println(username+"开始使用"+name+"秒杀商品失败了!!");}}}
}
案例二:取款业务
案列介绍
设计一个程序,使用两个线程模拟在两个地点同时从一个账号中取钱,假如卡中一共有1000元,每个线程取800元,要求演示结果一个线程取款成功,剩余200元,另一个线程取款失败,余额不足;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class GetMoney {public static void main(String[] args) {seckill();}public static void seckill(){ExecutorService executorService = Executors.newFixedThreadPool(2);//20个人参加秒杀for (int i = 1; i <= 2; i++) {executorService.submit(new MyTask());}executorService.shutdown();}
}
class MyTask implements Runnable{private static int money=1000;private static int qvMoney=800;public void run() {String name = Thread.currentThread().getName();synchronized (MyTask.class){if (money>800){System.out.println("线程"+name+"取了"+qvMoney+",还剩"+(money-=qvMoney));}else {System.out.println("线程"+name+"来取钱了,还剩"+money+",余额不足!");}}}
}
补充:核心工作线程是否会被回收?
线程池中有个allowCoreThreadTimeOut字段能够描述是否回收核心工作线程,线程池默认是false表示不回收核心线程,我们可以使用allowCoreThreadTimeOut(true)方法来设置线程池回收核心线程
四大拒绝策略
线程池中有哪些阻塞队列