目录
一.上节复习
1.阻塞队列
二.线程池
1.什么是线程池
2.为什么要使用线程池
3.JDK中的线程池
三.工厂模式
1.工厂模式的目的
四.使用线程池
1.submit()方法
2.模拟两个阶段任务的执行
五.自定义一个线程池
六.JDK提供线程池的详解
1.如何自定义一个线程池?
2.创建线程池的构造方法的参数及含义
3.描述线程池的工作原理
4.拒绝策略
5.为什么不推荐使用系统自带的线程池
七.自定义线程池
1.自定义线程池
2.拒绝策略测试
一.上节复习
上节内容指路:Java之阻塞队列和消息队列
1.阻塞队列
基于阻塞队列和生产者消费者模型可以实现消息队列
wait()和notify()使用的时机
虚假唤醒:把wait()的判断条件放在一个while循环里面,唤醒之后需要重新检查等待条件
二.线程池
1.什么是线程池
JDBC编程中,通过DataSource获取Connection就用到了池(数据库连接池)的概念 数据库连接池中有一些已经建立了连接(输入完账号密码的状态)的connection
当Java程序需要数据库连接时,只需要从数据库连接池中获取到一个空闲的连接进行使用
当Java程序使用完连接之后,就会将当前连接返还给数据库连接池
线程池里放的是线程本身,当程序启动时就创建若干个线程,如果有任务就处理,没有任务就阻塞等待.
2.为什么要使用线程池
为了减少系统创建线程的开销
如果我们不使用线程池,每一次使用创建线程,销毁线程(用户态到内核态),很浪费系统的资源,而且再多线程的环境下,我们一次使用可能是多个线程,多个一起创建很浪费系统的资源,大大减少了系统执行的效率.使用线程池,每一次到池中拿到一个线程,只需要涉及到用户态的操作.
3.JDK中的线程池
1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
2. 创建一个操作无界队列且初始线程数为n的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
无界队列: 指的是对队列中的元素个数不加限制,可能出现内存被消耗殆尽的情况
3. 创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务不保证处理顺序
ExecutorService executorService = Executors.newWorkStealingPool();
三.工厂模式
1.工厂模式的目的
解决构造方法创建对象的缺陷
以下情况会发生问题,(id,name)和(age,name)会出现相同的参数列表不能够重载,因此不能同时书写这两个构造方法
我们可以通过两个静态方法来进行创建
class Student {private int id;private int age;private String name;public static Student createByIdAndName(int id, String name) {Student student = new Student();student.setId(id);student.setName(name);return student;}public static Student createByAgeAndName(int age, String name) {Student student = new Student();student.setAge(age);student.setName(name);return student;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
四.使用线程池
1.submit()方法
通过submit方法可以看出我们可以提交任务到线程池,线程池中提供线程进行执行任务.
2.模拟两个阶段任务的执行
public class Demo03_ThreadPoolUse {public static void main(String[] args) throws InterruptedException {//创建一个初始大小为3的线程池ExecutorService threadPool = Executors.newFixedThreadPool(3);for (int i = 0; i {System.out.println("我是任务:" + taskId+","+Thread.currentThread().getName());});}//模拟线程等待一段时间TimeUnit.SECONDS.sleep(5);System.out.println("第二阶段开始");for (int i = 10; i {System.out.println("我是任务:" + taskId+","+Thread.currentThread().getName());});}}}
打印的内容:
注意:此时程序并没有结束,在等待其他任务提交到线程池进行执行
五.自定义一个线程池
1.可以提交任务到线程池,那么就需要一种数据结构来保存任务—-可以考虑使用阻塞队列
2.创建线程池需要指定初始的线程数量,这些线程不断的扫描阻塞队列,如果有任务就立即执行.
可以考虑使用线程池对象的构造方法,接收要创建线程的数据并在构造方法中完成线程的创建
public class MyThreadPool {//定义一个阻塞队列来保存要执行的任务BlockingQueue queue = new LinkedBlockingQueue(3);//提供一个方法,用来提交任务public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//构造方法完成线程的初始化public MyThreadPool(int capacity) {if (capacity <= 0) {throw new IllegalArgumentException("capacity is illegal");}//完成线程的创建,扫描队列,取出任务并执行for (int i = 0; i {while (true) {//取出任务try {Runnable take = queue.take();take.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}}}
测试自定义的线程池:
public class Demo04_MyThreadPool {public static void main(String[] args) throws InterruptedException {//创建一个初始大小为3的线程池MyThreadPool threadPool = new MyThreadPool(3);for (int i = 0; i {System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());});}//模拟线程等待一段时间TimeUnit.SECONDS.sleep(2);System.out.println("第二阶段开始");for (int i = 10; i {System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());});}}}
打印结果:
六.JDK提供线程池的详解
1.如何自定义一个线程池” />2.创建线程池的构造方法的参数及含义
- int corePoolSize 核心线程数,创建线程是包含的最小线程数
- int maximumPoolSize 最大线程数,也叫临时线程数(核心线程数不够时,允许创建最大的线程数)
- long keepAliveTime 临时空闲时长,超过这个时间自动释放
- TimeUnit unit 空闲的时间单位,和keepAliveTime一起使用
- BlockingQueue workQueue 用来保存任务的阻塞队列
- ThreadFactory threadFactory 线程工厂,如何去创建线程
- RejectedExecutionHandler handler 拒绝策略,触发的时机,当线程池处理不了过多的任务
3.描述线程池的工作原理
即上面七个参数如何搭配使用
先来举一个现实中的例子帮助我们理解工作原理:饭店老板根据人流量安排桌子数(线程数)
1.饭店里面初始有五张桌子(核心线程数为5)
2.下午3点,人数少于5,5张桌子足够接待顾客(核心线程数大于任务数)
3.到了晚饭点,人数大于5,不够接待顾客,顾客需要拿号排队等待(向阻塞队列加入任务)
4.排号人数过多,老板在饭店门口临时加了10张桌子(创建10个临时线程)
5.在用餐高峰期,门口10张桌子全部用完,排号也特别多,就让新来的顾客下次再来(执行拒绝策略)
6.当人数减少,门口10张桌子空了下来,过了半小时还没人来,将10张桌子回收(临时线程达到空闲时常,回收临时线程)
- 当任务添加到线程池中时,先判断任务数是否大于核心线程数,如果不大于,直接执行任务
- 任务数大于核心线程数,则加入阻塞队列
- 当阻塞队列满了之后,会创建临时线程,会按照最大线程数,一次性创建到最大线程数
- 当阻塞队列满了并且临时线程也创建完成,再提交任务,就会执行拒绝策略.
- 当任务减少,临时线程达到空闲时长时,会被回收.
4.拒绝策略
AbortPolicy:直接拒绝任务的加入,并且抛出RejectedExecutionException异常
CallerRunsPolicy:返回给提交任务的线程执行
DiscardOldestPolicy:舍弃最老的任务
DiscardPolicy:舍弃最新的任务
根据自己的业务场景选择合适的拒绝策略
5.为什么不推荐使用系统自带的线程池
1.由于使用了无界队列,可能会有内存耗尽的风险
默认是长度最大的阻塞队列
2.临时线程数无法控,也可能会存在资源耗尽
七.自定义线程池
1.自定义线程池
public class Demo_05_Create {public static void main(String[] args) throws InterruptedException {//创建线程池并指定参数ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 1, TimeUnit.SECONDS,new LinkedBlockingQueue(10), new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i {System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());});}//模拟线程等待一段时间TimeUnit.SECONDS.sleep(5);System.out.println("第二阶段开始");for (int i = 10; i {System.out.println("我是任务:" + taskId + "," + Thread.currentThread().getName());});}}}
2.拒绝策略测试
1.AbortPolicy
将阻塞队列的大小改为1,然后在执行方法
2.CallerRunsPolicy
返回给调用者main方法线程
3.DiscardOldestPolicy
抛弃了一部分的任务,老的
4.DiscardPolicy
抛弃了一部分的任务,新的