• Java中java.util.concurrent包下的4中线程池代码示例


    先来看下ThreadPool的类结构

    其中红色框住的是常用的接口和类(图片来自:https://blog.csdn.net/panweiwei1994/article/details/78617117?from=singlemessage)

    为什么需要线程池呢?
    我们在创建线程的时候,一般使用new Thread(),但是每次在启动一个线程的时候就new 一个Thread对象,会让性能变差(spring不都使用IOC管理对象了嘛)。还有其他的一些弊端:

    • 可能会造成无限创建线程对象,对象之间相互竞争资源,造成过多占用资源而宕机。
    • 缺乏相关功能,如定时执行、定期执行、线程中断。

    使用线程池的避免这些事情:

    • 重用存在的线程,减少对象创建、消亡的开销,性能佳。
    • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
    • 提供定时执行、定期执行、单线程、并发数控制等功能。

    线程池的种类:
    java通过Executor是提供4种线程池,分别为:
    1)newCachedThreadPool:创建一个可缓存的线程池,有任务来临时如果线程池中有空闲的线程,那么就使用空闲的线程执行任务(即线程是可以复用),如果没有空闲线程则创建新的线程执行任务。
    2)newFixedThreadPool:创建一个定长的线程池,线程池的线程数量固定,当任务来临,但是又没有空闲线程,则把任务放入队列中等待直到有空闲线程来处理它。
    3)newScheduledThreadPool:创建一个定长的线程,但是能支持定时或周期性的执行。
    4)newSingleThreadPool:创建一个单线程化的线程池,线程池中只有一个唯一的线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

    示例:
    1)newCachedThreadPool

     1 /**
     2    * 创建可缓存的线程池,线程池的线程可以重复利用,除非任务来不及处理就会创建新的线程。
     3    */
     4   public static void createCachedThreadPool(){
     5     ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
     6     for (int i = 0; i < 10; i++) {
     7       final int index = i;
     8       try {
     9         //在这里使主线程停下来,让启动的线程执行完Syso操作
    10         //并且有时间回收线程以确保下次启动的线程还是上次的线程
    11         Thread.sleep(index * 1000);// 
    12       } catch (InterruptedException e) {
    13         e.printStackTrace();
    14       }
    15       cachedThreadPool.execute(new Runnable() {
    16         @Override
    17         public void run() {
    18          /* try {
    19             //这里让启动的线程睡眠,保证下次启动的线程是新的线程,不是此时睡眠的。
    20             Thread.sleep(index * 1000);
    21           } catch (InterruptedException e) {
    22             // TODO Auto-generated catch block
    23             e.printStackTrace();
    24           }*/
    25           System.out.println(Thread.currentThread().getName() + " " + index);
    26         }
    27       });
    28 
    29     }
    30   }

    执行结果:

    结果显示,执行for循环输出的线程都是同一个,线程重复使用了。

    把注释的地方放开,并且注释上面的睡眠,执行结果:

    结果显示的是不同的线程名称执行的for循环,对比上面的执行结果的线程名称,可以得出结论:有任务来临时如果线程池中有空闲的线程,那么就使用空闲的线程执行任务(即线程是可以复用),如果没有空闲线程则创建新的线程执行任务

    2)newFixedThreadPool

     1  /**
     2    * 创建固定长度的线程池,超出的任务会在队列中进行等待,直到有线程空出来来执行。
     3    */
     4   public static void createFixedThreadPool() {
     5     ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
     6     for (int i = 0; i < 10; i++) {
     7       final int index = i;
     8       fixedThreadPool.execute(new Runnable() {
     9         @Override
    10         public void run() {
    11           try {
    12             System.out.println(Thread.currentThread().getName() + "," + index);
    13             Thread.sleep(2000);
    14           } catch (InterruptedException e) {
    15             e.printStackTrace();
    16           }
    17         }
    18       });
    19     }
    20   }

    运行结果:

    创建了固定长度是3的线程池,输出前3行之后,发现线程都在sleep(),要执行的输出任务没有找到对应的执行线程,任务就会放入队列中进行等待,等待某个线程执行完毕后,再去执行任务。(线程池中的线程也是重复使用的)

    3)newScheduledThreadPool

    3.1)延迟执行某个线程

     1 public static void createScheduledThreadPoolToDelay(){
     2     ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
     3     for(int i = 0; i < 10; i++){
     4       final int index = i;
     5       try {
     6         Thread.sleep(2000);
     7       } catch (InterruptedException e) {
     8         e.printStackTrace();
     9       }
    10       scheduledThreadPool.schedule(new Runnable(){
    11         @Override
    12         public void run() {
    13           System.out.println(Thread.currentThread().getName() + " " + index + " delay 3 seconds");
    14         }
    15       }, 3, TimeUnit.SECONDS);
    16     }
    17   }

    执行结果:

    延迟3+2秒执行(3秒是newScheduledThreadPool中设置的,2秒是Thread.sleep()设置的),结果中可以看出主线程睡眠2秒并不能保证newScheduledThreadPool线程池中是使用旧线程执行任务还是新建线程执行任务,这种情况是随机的。(这点和newCachedThreadPool不一样,newCachedThreadPool是用就线程)

     3.2)定期执行某个任务

     1  public static void createScheduledThreadPoolToFixRate(){
     2     ScheduledExecutorService exe = Executors.newScheduledThreadPool(3);
     3     exe.scheduleAtFixedRate(new Runnable(){
     4       @Override
     5       public void run() {
     6         System.out.println(Thread.currentThread().getName() + " delay 1 seconds, and excute every 3 seconds");
     7       }
     8     }, 1, 3, TimeUnit.SECONDS);
     9     
    10   }

    执行结果:

    结果是延迟1s启动线程,并且之后每隔3s重复执行任务,但是用的是同一个线程。

    4)newSingleThreadPool

     1 /**
     2    * 创建一个单线程的线程池
     3    */
     4   public static void createSingleThreadPool(){
     5     ExecutorService exe = Executors.newSingleThreadExecutor();
     6     for(int i = 0; i < 10; i++){
     7       final int index = i;
     8       exe.execute(new Runnable() {
     9         @Override
    10         public void run() {
    11             try {
    12                 System.out.println(Thread.currentThread().getName() + ", " +index);
    13                 Thread.sleep(2000);// 让当前线程睡眠2s,发现顺序打印1~10,并且有个打印停顿2s
    14             } catch (InterruptedException e) {
    15                 e.printStackTrace();
    16             }
    17         }
    18     });
    19     }
    20   }

    运行结果:

    每输出一行结果就等待2s,可以看出每次输出的线程名都一样,说使用的同一个线程。单线程化的线程池中只有一个线程。

    总结:
    上面就是4中线程池的实现及其使用示例,和他们之间的区别。

    其中newFixedThreadPool()有个坑,最好不要使用Executor.newFixedThreadPool(int nThreads)来创建线程池,因为它使用了LinkedBlockingQueue,容量是Integer.MAX_VALUE,容量太大容易造成防止所有任务都被阻塞,从而导致死锁。下面是具体源码:

       public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
        /**
         * Creates a <tt>LinkedBlockingQueue</tt> with a capacity of
         * {@link Integer#MAX_VALUE}.
         */
        public LinkedBlockingQueue() {
            this(Integer.MAX_VALUE);
        }

    应该尽量直接使用new ThreadPoolExecutor来创建线程池,并指定阻塞队列的容量。

    参考文章:https://www.cnblogs.com/zhaoyan001/p/7049627.html

  • 相关阅读:
    [转载]Linux下mail使用技巧
    VTK Online教程大全
    拓扑结构与TopoDS
    三维视图变换与相机模型
    说说DoDataExchange(CDataExchange* pDX)
    Lua Lib在VC下的编译
    建立最简单的OpenCASCADE程序
    构建通用类型 继承 VS 聚合
    【软件】新瓶装老酒 MyCapture
    用std::find查找文件流中的内容
  • 原文地址:https://www.cnblogs.com/teiba/p/10608890.html
Copyright © 2020-2023  润新知