彻底搞懂Java多线程(三)

时间:2022-09-10 14:05:22

Java线程池

线程的缺点:

1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。

2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。

所以就有了线程池:

使用池化的而技术来管理和使用线程。

线程池的优点

1.可以避免频繁的创建和销毁线程

2.可以更好的管理线程的个数和资源的个数。

3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。

4.线程池可以更友好的拒绝不能处理的任务。

线程池的6种创建方式

一共有7种创建方式

创建方式一:

创建固定个数的线程池:

  1. package ThreadPoolDemo;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /**
  5. * user:ypc;
  6. * date:2021-06-13;
  7. * time: 10:24;
  8. */
  9. public class ThreadPoolDemo1 {
  10. public static void main(String[] args) {
  11. //创建一个固定个数的线程池
  12. ExecutorService executorService = Executors.newFixedThreadPool(10);
  13. //执行任务
  14. for (int i = 0; i < 10; i++) {
  15. executorService.execute(new Runnable() {
  16. @Override
  17. public void run() {
  18. System.out.println("线程名" + Thread.currentThread().getName());
  19. }
  20. });
  21. }
  22. }
  23. }

彻底搞懂Java多线程(三)

那么如果执行次数大于10次呢?

线程池不会创建新的线程,它会复用之前的线程。

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?

我们可以使用Jconsole来看一看:

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

结果是只有2个线程被创建。

创建方式二:

创建带有缓存的线程池:

适用于短期有大量的任务的时候使用

  1. public class ThreadPoolDemo2 {
  2. public static void main(String[] args) {
  3. //创建带缓存的线程池
  4. ExecutorService executorService = Executors.newCachedThreadPool();
  5. for (int i = 0; i < 100; i++) {
  6. executorService.execute(new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(Thread.currentThread().getName());
  10. }
  11. });
  12. }
  13. }
  14. }

彻底搞懂Java多线程(三)

方式三:

创建执行定时任务的线程池

  1. package ThreadPoolDemo;
  2. import java.util.Date;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.ScheduledExecutorService;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * user:ypc;
  8. * date:2021-06-13;
  9. * time: 11:32;
  10. */
  11. public class ThreadPoolDemo3 {
  12. public static void main(String[] args) {
  13. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
  14. System.out.println("执行定时任务前的时间:" + new Date());
  15. scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
  16. @Override
  17. public void run() {
  18. System.out.println("执行任务的时间:" + new Date());
  19. }
  20. },1,2, TimeUnit.SECONDS);
  21. }
  22. }

彻底搞懂Java多线程(三)

执行任务的四个参数的意义:

参数1:延迟执行的任务

参数2:延迟一段时间后执行

参数3:定时任务执行的频率

参数4:配合前两个参数使用,是2、3参数的时间单位

还有两种执行的方法:

只会执行一次的方法:

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

第三种的执行方式:

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

那么这种的执行方式和第一种的执行方式有什么区别呢?

当在两种执行的方式中分别加上sleep()之后:

彻底搞懂Java多线程(三)

方式一:

彻底搞懂Java多线程(三)

方式三:

彻底搞懂Java多线程(三)

结论很明显了:

第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间

第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。

创建方式四:

  1. package ThreadPoolDemo;
  2. import java.util.Date;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.ScheduledExecutorService;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * user:ypc;
  8. * date:2021-06-13;
  9. * time: 12:38;
  10. */
  11. public class ThreadPoolDemo4 {
  12. public static void main(String[] args) {
  13. //创建单个执行任务的线程池
  14. ScheduledExecutorService scheduledExecutorService
  15. = Executors.newSingleThreadScheduledExecutor();
  16. System.out.println("执行任务之前" + new Date());
  17. scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
  18. @Override
  19. public void run() {
  20. System.out.println("我是SingleThreadSchedule"+ new Date());
  21. }
  22. },3,1, TimeUnit.SECONDS);
  23. }
  24. }

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

创建方式五:

创建单个线程的线程池

  1. package ThreadPoolDemo;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /**
  5. * user:ypc;
  6. * date:2021-06-13;
  7. * time: 12:55;
  8. */
  9. public class ThreadPoolDemo5 {
  10. public static void main(String[] args) {
  11. //创建单个线程的线程池
  12. ExecutorService executorService = Executors.newSingleThreadExecutor();
  13. for (int i = 0; i < 20; i++) {
  14. executorService.execute(new Runnable() {
  15. @Override
  16. public void run() {
  17. System.out.println("线程名 " + Thread.currentThread().getName());
  18. }
  19. });
  20. }
  21. }
  22. }

彻底搞懂Java多线程(三)

创建单个线程池的作用是什么?

1.可以避免频繁创建和销毁线程所带来的性能的开销

2.它有任务队列,可以存储多余的任务

3.可以更好的管理任务

4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略

创建方式六:

创建异步线程池根据当前CPU来创建对应个数的线程池

  1. package ThreadPoolDemo;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /**
  5. * user:ypc;
  6. * date:2021-06-13;
  7. * time: 13:12;
  8. */
  9. public class ThreadPoolDemo6 {
  10. public static void main(String[] args) {
  11. ExecutorService executorService = Executors.newWorkStealingPool();
  12. for (int i = 0; i < 10; i++) {
  13. executorService.execute(new Runnable() {
  14. @Override
  15. public void run() {
  16. System.out.println("线程名" + Thread.currentThread().getName());
  17. }
  18. });
  19. }
  20. }
  21. }

彻底搞懂Java多线程(三)

运行结果为什么什么都没有呢?

看下面的异步与同步的区别就知道了。

加上这个

彻底搞懂Java多线程(三)

就可以输出结果了

彻底搞懂Java多线程(三)

线程池的第七种创建方式

前六种的创建方式有什么问题呢?

1.线程的数量不可控(比如带缓存的线程池)

2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。

所以就可以使用下面的创建线程池的方式了:

  1. package ThreadPoolDemo;
  2. import java.util.concurrent.LinkedBlockingDeque;
  3. import java.util.concurrent.ThreadFactory;
  4. import java.util.concurrent.ThreadPoolExecutor;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * user:ypc;
  8. * date:2021-06-13;
  9. * time: 15:05;
  10. */
  11. public class ThreadPoolDemo7 {
  12. private static int threadId = 0;
  13. public static void main(String[] args) {
  14. ThreadFactory threadFactory = new ThreadFactory() {
  15. @Override
  16. public Thread newThread(Runnable r) {
  17. Thread thread = new Thread(r);
  18. thread.setName("我是threadPool-" + ++threadId);
  19. return thread;
  20. }
  21. };
  22. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
  23. TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
  24. threadFactory, new ThreadPoolExecutor.AbortPolicy());
  25. for (int i = 0; i < 15; i++) {
  26. threadPoolExecutor.execute(new Runnable() {
  27. @Override
  28. public void run() {
  29. System.out.println(Thread.currentThread().getName());
  30. }
  31. });
  32. }
  33. }
  34. }

彻底搞懂Java多线程(三)

参数说明:

彻底搞懂Java多线程(三)

  • 参数一:核心线程数|线程池正常情况下的线程 数量
  • 参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数
  • 参数三:最大线程的存活时间
  • 参数四:配合参数三一起使用的表示参数三的时间单位
  • 参数五:任务队列
  • 参数六:线程工厂
  • 参数七:决绝策略

注意事项:最大的线程数要大于等于核心的线程数

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

五种拒绝策略

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?

因为LinkedBlockingDeque时FIFO的。

第五种:自定义的拒绝策略

彻底搞懂Java多线程(三)

彻底搞懂Java多线程(三)

ThreadPoolExecutor的执行方式

彻底搞懂Java多线程(三)

  1. package ThreadPoolDemo;
  2. import java.util.concurrent.*;
  3. /**
  4. * user:ypc;
  5. * date:2021-06-13;
  6. * time: 16:58;
  7. */
  8. public class ThreadPoolDemo9 {
  9. public static void main(String[] args) throws ExecutionException, InterruptedException {
  10. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
  11. TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
  12.  
  13. //线程池的执行方式一
  14. threadPoolExecutor.execute(new Runnable() {
  15. @Override
  16. public void run() {
  17. System.out.println("使用了execute()执行了线程池");
  18. }
  19. });
  20. //线程池的执行方式二
  21. Future<String> futureTask =
  22. threadPoolExecutor.submit(new Callable<String>() {
  23. @Override
  24. public String call() throws Exception {
  25. return "使用submit(new Callable<>())执行了线程池";
  26. }
  27. });
  28. System.out.println(futureTask.get());
  29.  
  30. }
  31. }

无返回值的执行方式

彻底搞懂Java多线程(三)

有返回值的执行方式

彻底搞懂Java多线程(三)

ThreadPoolExecutor的执行流程

当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务

当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存

放到任务队列中

如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。

如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。