当前位置 : 主页 > 编程语言 > java >

彻底搞懂Java多线程(三)

来源:互联网 收集:自由互联 发布时间:2021-08-21
目录 Java线程池 线程池的优点 线程池的6种创建方式 创建单个线程池的作用是什么? 线程池的第七种创建方式 ThreadPoolExecutor的执行方式 ThreadPoolExecutor的执行流程 线程池的终止 线程池
目录
  • Java线程池
    • 线程池的优点
    • 线程池的6种创建方式
    • 创建单个线程池的作用是什么?
    • 线程池的第七种创建方式
    • ThreadPoolExecutor的执行方式
    • ThreadPoolExecutor的执行流程
    • 线程池的终止
    • 线程池的状态
    • 异步、同步
      • 1.Java 线程 同步与异步
    • 线程工厂
    • 总结

      Java线程池

      线程的缺点:

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

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

      所以就有了线程池:

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

      线程池的优点

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

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

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

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

      线程池的6种创建方式

      一共有7种创建方式

      创建方式一:

      创建固定个数的线程池:

      package ThreadPoolDemo;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 10:24;
       */
      public class ThreadPoolDemo1 {
          public static void main(String[] args) {
              //创建一个固定个数的线程池
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              //执行任务
              for (int i = 0; i < 10; i++) {
                  executorService.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("线程名" + Thread.currentThread().getName());
                      }
                  });
              }
          }
      }
      

      在这里插入图片描述

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

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

      在这里插入图片描述

      在这里插入图片描述

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

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

      在这里插入图片描述

      在这里插入图片描述

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

      创建方式二:

      创建带有缓存的线程池:

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

      public class ThreadPoolDemo2 {
          public static void main(String[] args) {
              //创建带缓存的线程池
              ExecutorService executorService = Executors.newCachedThreadPool();
              for (int i = 0; i < 100; i++) {
                  executorService.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println(Thread.currentThread().getName());
                      }
                  });
              }
          }
      }
      

      在这里插入图片描述

      方式三:

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

      package ThreadPoolDemo;
      import java.util.Date;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 11:32;
       */
      public class ThreadPoolDemo3 {
          public static void main(String[] args) {
              ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
              System.out.println("执行定时任务前的时间:" + new Date());
              scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("执行任务的时间:" + new Date());
                  }
              },1,2, TimeUnit.SECONDS);
          }
      }
      

      在这里插入图片描述

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

      参数1:延迟执行的任务

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

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

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

      还有两种执行的方法:

      只会执行一次的方法:

      在这里插入图片描述

      在这里插入图片描述

      第三种的执行方式:

      在这里插入图片描述

      在这里插入图片描述

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

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

      在这里插入图片描述

      方式一:

      在这里插入图片描述

      方式三:

      在这里插入图片描述

      结论很明显了:

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

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

      创建方式四:

      package ThreadPoolDemo;
      import java.util.Date;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 12:38;
       */
      public class ThreadPoolDemo4 {
          public static void main(String[] args) {
              //创建单个执行任务的线程池
              ScheduledExecutorService scheduledExecutorService
                      = Executors.newSingleThreadScheduledExecutor();
              System.out.println("执行任务之前" + new Date());
              scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("我是SingleThreadSchedule"+ new Date());
                  }
              },3,1, TimeUnit.SECONDS);
          }
      }
      

      在这里插入图片描述

      在这里插入图片描述

      创建方式五:

      创建单个线程的线程池

      package ThreadPoolDemo;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 12:55;
       */
      public class ThreadPoolDemo5 {
          public static void main(String[] args) {
              //创建单个线程的线程池
              ExecutorService executorService = Executors.newSingleThreadExecutor();
              for (int i = 0; i < 20; i++) {
                  executorService.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("线程名 " +  Thread.currentThread().getName());
                      }
                  });
              }
          }
      }
      

      在这里插入图片描述

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

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

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

      3.可以更好的管理任务

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

      创建方式六:

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

      package ThreadPoolDemo;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 13:12;
       */
      public class ThreadPoolDemo6 {
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newWorkStealingPool();
              for (int i = 0; i < 10; i++) { 
                  executorService.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("线程名" + Thread.currentThread().getName());
                      }
                  });
              }
          }
      }
      

      在这里插入图片描述

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

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

      加上这个

      在这里插入图片描述

      就可以输出结果了

      在这里插入图片描述

      线程池的第七种创建方式

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

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

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

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

      package ThreadPoolDemo;
      import java.util.concurrent.LinkedBlockingDeque;
      import java.util.concurrent.ThreadFactory;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 15:05;
       */
      public class ThreadPoolDemo7 {
          private static int threadId = 0;
          public static void main(String[] args) {
              ThreadFactory threadFactory = new ThreadFactory() {
                  @Override
                  public Thread newThread(Runnable r) {
                      Thread thread = new Thread(r);
                      thread.setName("我是threadPool-" + ++threadId);
                      return thread;
                  }
              };
              ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
                      TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
                      threadFactory, new ThreadPoolExecutor.AbortPolicy());
              for (int i = 0; i < 15; i++) {
                  threadPoolExecutor.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println(Thread.currentThread().getName());
                      }
                  });
              }
          }
      }
      

      在这里插入图片描述

      参数说明:

      在这里插入图片描述

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

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

      在这里插入图片描述

      在这里插入图片描述

      五种拒绝策略

      在这里插入图片描述

      在这里插入图片描述

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

      因为LinkedBlockingDeque时FIFO的。

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

      在这里插入图片描述

      在这里插入图片描述

      ThreadPoolExecutor的执行方式

      在这里插入图片描述

      package ThreadPoolDemo;
      import java.util.concurrent.*;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 16:58;
       */
      public class ThreadPoolDemo9 {
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
                      TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
      
              //线程池的执行方式一
              threadPoolExecutor.execute(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("使用了execute()执行了线程池");
                  }
              });
              //线程池的执行方式二
              Future<String> futureTask =
                      threadPoolExecutor.submit(new Callable<String>() {
                          @Override
                          public String call() throws Exception {
                              return "使用submit(new Callable<>())执行了线程池";
                          }
                      });
              System.out.println(futureTask.get());
              
          }
      }
      

      无返回值的执行方式

      在这里插入图片描述

      有返回值的执行方式

      在这里插入图片描述

      ThreadPoolExecutor的执行流程

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

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

      放到任务队列中

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

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

      在这里插入图片描述

      线程池的终止

      shutdown()

      线程池的任务会执行完

      shutdownNow()

      立即终止线程池,线程池的任务不会执行完

      线程池的状态

      在这里插入图片描述

      异步、同步

      1.Java 线程 同步与异步

      多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制

      同步

      A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

      异步

      A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待同步的方式:

      1.发送请求

      2.等待执行完成

      3.有结果的返回

      异步的方式

      1.发请求

      2.执行完成

      3.另一个线程异步处理

      4.处理完成之后返回回调结果

      显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,使用异步的机制,性能会有所提升

      线程工厂

      设想这样一种场景,我们需要一个线程池,并且对于线程池中的线程对象,赋予统一的线程优先级、统一的名称、甚至进行统一的业务处理或和业务方面的初始化工作,这时工厂方法就是最好用的方法了

      package ThreadPoolDemo;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ThreadFactory;
      /**
       * user:ypc;
       * date:2021-06-13;
       * time: 11:12;
       */
      public class ThreadFactoryDemo {
          public static void main(String[] args) {
              MyThreadFactory myThreadFactory = new MyThreadFactory();
              ExecutorService executorService =  Executors.newFixedThreadPool(10,myThreadFactory);
              for (int i = 0; i < 10; i++) {
                  executorService.execute(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println("使用线程工厂设置的线程名:"+ Thread.currentThread().getName() +
                                  " 使用线程工厂设置的线程的优先级" + Thread.currentThread().getPriority());
                      }
                  });
              }
      
          }
          private static int count = 0;
           static class MyThreadFactory implements ThreadFactory{
               @Override
               public Thread newThread(Runnable r) {
                   Thread thread = new Thread(r);
                   thread.setPriority(8);
                   thread.setName("thread--" + count++);
                   return thread;
               }
           }
      }
      

      在这里插入图片描述

      总结

      本篇文章就到这里了,希望可以对你有所帮助,也希望您能够多多关注自由互联的更多内容!

      网友评论