teiba

导航

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

posted on 2019-03-27 17:08  teiba  阅读(307)  评论(0编辑  收藏  举报