zoukankan      html  css  js  c++  java
  • 四种线程池的使用(JAVA笔记-线程基础篇)

    线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。——百度百科

    • 简单来说,线程池(thread pool)就像池子一样,不过池子里面可能放的是水,需要水就去池子里打水。而线程池里面放的是线程,需要线程的时候就去线程池里取线程。因为一个线程可能只执行一个很小的功能,比如计算个1+1等于几,如果每次都重新创建一个新线程计算,用完就把线程给销毁了,这样很浪费效率(创建和销毁线程很费时费力)。
    • 如果我创建一个线程去计算1+1,计算完把这条线程放到线程池里面,让线程池帮我维护,别让程序给销毁了。下次计算1+2的时候,直接跟线程池要线程就好,不用再创建了。

    JAVA提供的线程池工具类

    在JAVA的java.util.concurrent包(大佬们都叫这个包JUC)下面有一个工具类Executors。这个类封装了创建四种线程池的方法。底层都是通过new ThreadPoolExecutor(...)来实现ExecutorService对象的。

    1. newSingleThreadExecutor()单线程池

      • 作用:创建一个只有一个线程的线程池,如果有超过一个的任务进来,就放在队列中等待。等上一个任务执行完再来执行下一个。
      • 适用范围:适合长期执行的任务,或者需要按照顺序执行的一系列任务。

      创建线程池

       ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
      

      为了使用这个线程池,我们创建3个Thread对象,也就是线程需要执行的任务。

      Thread thread1=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!");
           }
       });
       Thread thread2=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!");
           }
       });
       Thread thread3=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!");
           }
       });
      

      使用刚才创建的线程池来执行thread对象。

      singleThreadExecutor.execute(thread1);
      singleThreadExecutor.execute(thread2);
      singleThreadExecutor.execute(thread3);
      

      完整代码

      public class MyTest {
           public static void main(String[] args) {
               ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
      
               Thread thread1=new Thread(new Runnable() {
               public void run() {
                   //使用getName()获取执行这段代码的线程名。下同
                   System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
               }
               });
               Thread thread2=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
                   }
               });
               Thread thread3=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
                   }
               });
               singleThreadExecutor.execute(thread1);
               singleThreadExecutor.execute(thread2);
               singleThreadExecutor.execute(thread3);
           }
       }
      

      输出:
      pool-1-thread-1 使用线程池中的线程执行的!
      pool-1-thread-1 使用线程池中的线程执行的!
      pool-1-thread-1 使用线程池中的线程执行的!

      看!上面输出的结果很好玩,线程名称都是pool-1-thread-1
      如果我们不用singleThreadExecutor.execute(thread1);而直接使用thread1.start();会怎么样呢?
      直接改代码

      public class MyTest {
           public static void main(String[] args) {
               Thread thread1=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
                   }
               });
               Thread thread2=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
                   }
               });
               Thread thread3=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+"  使用线程池中的线程执行的!");
                   }
               });
               //这里跟上面的例子不一样
               thread1.start();
               thread2.start();
               thread3.start();
           }
       }
      

      输出结果:
      Thread-0 使用线程池中的线程执行的!
      Thread-2 使用线程池中的线程执行的!
      Thread-1 使用线程池中的线程执行的!

      这次运行三段代码的线程名不同了,也就是说是三个不同的线程。
      而且更有意思的是,如果多运行几遍,这三个线程输出顺序也可能会改变。这就说明他们三个线程被CPU“翻牌”的概率是不一定的,并不是哪个线程先.start()哪个线程里的代码先执行完。有时候Thread-0更受宠一点,而有时候Thread-2更受宠一点。

      结论:

      • newSingleThreadExecutor()创建的线程池里面只有一个可用线程。
      • 任务的执行是按照添加的顺序执行的。
      • 如果线程正在执行。这时候有其他的任务进来,需要线程执行,就会把其放在队列中等待。队列是五界的。

        队列无界,通俗的说就是来者不拒,不管执不执行,你既然任务来了,就放在队列里等着吧。等这一个线程挨个的执行。
        细想这个线程池是不是又问题。来者不拒?我给你一百万、一千万、一亿个任务呢??你内存不是爆表了嘛?就OOM拉。

    2. newFixedThreadPool()

      • 作用:创建一个有固定大小的线程池,也就是指定线程池里面的线程数量。
      • 适合场景:适合长期执行的任务,并且希望控制线程数量。

      创建一个有两个线程的线程池

      ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
      

      写三个thread对象来测试一下

      public class MyTest {
           public static void main(String[] args) {
               ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
               Thread thread1=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+" 使用线程池中的线程执行的!thread1");
                   }
               });
               Thread thread2=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+" 使用线程池中的线程执行的!thread2");
                   }
               });
               Thread thread3=new Thread(new Runnable() {
                   public void run() {
                       System.out.println(Thread.currentThread().getName()+" 使用线程池中的线程执行的!thread3");
                   }
               });
               fixedThreadPool.execute(thread1);
               fixedThreadPool.execute(thread2);
               fixedThreadPool.execute(thread3);
           }
       }
      

      输出:
      pool-1-thread-1 使用线程池中的线程执行的!thread1
      pool-1-thread-2 使用线程池中的线程执行的!thread2
      pool-1-thread-1 使用线程池中的线程执行的!thread3

      可以看出pool-1-thread-1执行了thread1thread3的代码,而pool-1-thread-2执行了thread2的代码。这也说明线程池里至少有两个线程。

      结论:

      • newFixedThreadPool()创建一个拥有固定线程数量的线程池,线程数量通过传参设置。如:Executors.newFixedThreadPool(2)
    3. newCachedThreadPool()缓存型线程池

      • 作用:创建一个缓存型的线程池,有新任务进来,先查找有没有空闲的线程,如果有拿过来用,如果没有,就会创建新的线程并放到核心线程池里。
        此线程池里的线程存活是有一定时间的,如果一个线程空闲了一定时间没有被使用,就会被销毁。
        所以这个线程池里的线程不会太多空闲,也不会有不足。
      • 使用场景:适合执行周期短而多的任务。
        创建一个缓存型线程池
      ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
      

      用法上同。

    4. newScheduledThreadPool()计划型线程池

      • 作用:创建一个固定大小的线程池,线程池内的线程存活周期无限长,支持定时或者周期性的执行某个任务(比如隔3秒执行一次)等。
      • 使用场景:有周期性或者定时执行某个任务的需要。

      声明一个有三个线程的计划型线程池

      ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
      

      创建三个任务,也就是Thread对象。

      Thread thread1=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread1");
           }
       });
       Thread thread2=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread2");
           }
       });
       Thread thread3=new Thread(new Runnable() {
           public void run() {
               System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread3");
           }
       });
      

      十秒后执行任务1,thread1

      scheduledExecutorService.schedule(thread1,10,TimeUnit.SECONDS);
      

      说明:
      第一个参数是任务,也就是thread实例对象
      第二个参数是一个数字,跟第三个参数组合使用来确定多长时间后执行。
      第三个参数是一个TimeUnit的枚举类型。TimeUnit.SECONDS是秒、TimeUnit.HOURS是小时等等。

      10秒后开始,每个3秒执行一遍任务2

      scheduledExecutorService.scheduleAtFixedRate(thread2,10,3,TimeUnit.SECONDS);
      

      比上面的多了一参数,就是第三个(上面例子中的3),这个参数代表每隔多长时间执行一次任务。

      完整测试代码:

      public class MyTest {
          public static void main(String[] args) {
              ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
              Thread thread1=new Thread(new Runnable() {
                  public void run() {
                      System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread1");
                  }
              });
              Thread thread2=new Thread(new Runnable() {
                  public void run() {
                      System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread2");
                  }
              });
              Thread thread3=new Thread(new Runnable() {
                  public void run() {
                      System.out.println(Thread.currentThread().getName()+"使用线程池中的线程执行的!thread3");
                  }
              });
              //10秒后执行任务1
              scheduledExecutorService.schedule(thread1,10,TimeUnit.HOURS);
              //10秒后开始,每个3秒执行一遍任务2
              scheduledExecutorService.scheduleAtFixedRate(thread2,10,3,TimeUnit.SECONDS);
              //没有任何特点的执行任务3
              scheduledExecutorService.execute(thread3);
          }
      }
      

    结尾

    很多大佬都不推荐直接使用Executors封装好的实现线程的方法,因为在大型项目中会暴露出很多问题。比如我们常用的newFixedThreadPool()它的储存多余任务的队列是无边界的。
    Executors中的源码是这么实现的:

    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, 
                                      nThreads,
                                      0L,
                                      TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    

    先不用管其他的参数,看看第五个参数new LinkedBlockingQueue<Runnable>(),它是一个无界的队列。所以就可以无限制的往里面增加等待任务。这样添加个几千万几百万的不久内存溢出了嘛。

    其他的也多多少少存在一些问题。所以大佬们推荐自己动手创建自定义的线程池。

    如果有缘,我们下一篇笔记见。

    作者:BobC

    文章原创。如你发现错误,欢迎指正,在这里先谢过了。博主的所有的文章、笔记都会在优化并整理后发布在个人公众号上,如果我的笔记对你有一定的用处的话,欢迎关注一下,我会提供更多优质的笔记的。
  • 相关阅读:
    密码学-网站的安全登录认证设计
    密码学-软件加密技术和注册机制
    密码学-数字证书原理
    Linux-ssh证书登录(实例详解)
    unity, multi collider
    unity, 相机空间 与 相机gameObject的局部空间
    unity, WaterProDaytime注意事项。
    unity, 欧拉角(euler angle)
    unity, mono断点
    unity5, import fbx注意事项
  • 原文地址:https://www.cnblogs.com/Eastry/p/13033888.html
Copyright © 2011-2022 走看看