目录

一.上节复习

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.创建线程池的构造方法的参数及含义

  1. int corePoolSize 核心线程数,创建线程是包含的最小线程数
  2. int maximumPoolSize 最大线程数,也叫临时线程数(核心线程数不够时,允许创建最大的线程数)
  3. long keepAliveTime 临时空闲时长,超过这个时间自动释放
  4. TimeUnit unit 空闲的时间单位,和keepAliveTime一起使用
  5. BlockingQueue workQueue 用来保存任务的阻塞队列
  6. ThreadFactory threadFactory 线程工厂,如何去创建线程
  7. RejectedExecutionHandler handler 拒绝策略,触发的时机,当线程池处理不了过多的任务

3.描述线程池的工作原理

即上面七个参数如何搭配使用

先来举一个现实中的例子帮助我们理解工作原理:饭店老板根据人流量安排桌子数(线程数)

1.饭店里面初始有五张桌子(核心线程数为5)

2.下午3点,人数少于5,5张桌子足够接待顾客(核心线程数大于任务数)

3.到了晚饭点,人数大于5,不够接待顾客,顾客需要拿号排队等待(向阻塞队列加入任务)

4.排号人数过多,老板在饭店门口临时加了10张桌子(创建10个临时线程)

5.在用餐高峰期,门口10张桌子全部用完,排号也特别多,就让新来的顾客下次再来(执行拒绝策略)

6.当人数减少,门口10张桌子空了下来,过了半小时还没人来,将10张桌子回收(临时线程达到空闲时常,回收临时线程)

  1. 当任务添加到线程池中时,先判断任务数是否大于核心线程数,如果不大于,直接执行任务
  2. 任务数大于核心线程数,则加入阻塞队列
  3. 当阻塞队列满了之后,会创建临时线程,会按照最大线程数,一次性创建到最大线程数
  4. 当阻塞队列满了并且临时线程也创建完成,再提交任务,就会执行拒绝策略.
  5. 当任务减少,临时线程达到空闲时长时,会被回收.

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

抛弃了一部分的任务,新的