在并发编程中线程池的使用

在并发编程中线程池的使用

一、为什么要使用线程池    当需要处理的任务较少时,我们可以自己创建线程去处理,但在高并发场景下,我们需要处理的任务数量很多,由于创建销毁线程开销很大,这样频繁创建线程就会大大降低系统的效率。  此时,我们就可以使用线程池,线程池中的线程执行完一个任务后可以复用,并不被销毁。合理使用线程池有以下几点好处:

  1、减少资源的开销。通过复用线程,降低创建销毁线程造成的消耗。

  2、多个线程并发执行任务,提高系统的响应速度。 

 3、可以统一的分配,调优和监控线程,提高线程的可管理性。

二、怎样使用线程池

  1、创建线程池

     通过ThreadPoolExecutor来创建一个线程池。new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory, handler);

       java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,它有四个构造方法。

       public class ThreadPoolExecutor extends AbstractExecutorService {

     .....    

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueueworkQueue);

            public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, 

               BlockingQueueworkQueue,ThreadFactory threadFactory);

            public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

                BlockingQueueworkQueue,RejectedExecutionHandler handler); 

           public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

                BlockingQueueworkQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler); 

               ... 

       }    

         由源码可知,前三个构造器都是调用第四个构造器进行初始化。

    各参数含义: 

 . corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

  . maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

  . keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  . TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

  . runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。    (1) ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。    (2) LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。    (3) SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。    (4) PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

      .ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。    .Reje、ctedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n  AbortPolicy:直接抛出异常。   

    (1) CallerRunsPolicy:只用调用者所在线程来运行任务。   

    (2) DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    (3) DiscardPolicy:不处理,丢弃掉。 

   (4) 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

  2、向线程池提交任务

        使用execute提交任务,但execute()方法没有返回值,无法判断任务是否被线程池执行成功。execute方法输入的任务是一个Runnable类的实例。

              execute(new Runnable() { 

                    @Override

                     public void run() {       

                           // TODO Auto-generated method stub

                      }

              });   

       使用submit()方法提交任务,会返回一个future,那么我们可以通过这个future来判断任务是否执行成功。

  3、线程池的关闭

    调用shutdown()和shutdownNow()方法关闭线程池。

  4、使用示例

    这里我们选用第一种构造方法演示。

         public class ThreadPoolTest { 

                 public static void main(String[] args) {  

                          ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(5));

                     for(int i=0;i<15;i++){

                        Task task = new Task(i);

                        executor.execute(task);

                        System.out.println("线程池中线程数:" + executor.getPoolSize() + ",队列中等待执行的任务数:" + executor.getQueue().size() +

                      ",已执行完的任务数:" + executor.getCompletedTaskCount());

                   }

                  executor.shutdown();

             }

  }

  class Task implements Runnable {

          private int taskNum;

          public Task(int num) {

          this.taskNum = num;

          }

         @Override

          public void run() {

              System.out.println("task"+taskNum+“开始执行”);

              try{

                      Thread.currentThread().sleep(4000);

               }catch(InterruptedException e){

                     e.printStackTrace();

               }

               System.out.println("task"+taskNum+“执行完毕”);

          }

    }

}

执行结果:

               task0开始执行

               线程池中线程数:1,队列中等待执行的任务数:0,已执行完的任务数:0

               线程池中线程数:2,队列中等待执行的任务数:0,已执行完的任务数:0

               task1开始执行

               线程池中线程数:3,队列中等待执行的任务数:0,已执行完的任务数:0

               task2开始执行

               线程池中线程数:4,队列中等待执行的任务数:0,已执行完的任务数:0

               task3开始执行

                线程池中线程数:5,队列中等待执行的任务数:0,已执行完的任务数:0

                task4开始执行

                线程池中线程数:5,队列中等待执行的任务数:1,已执行完的任务数:0

                线程池中线程数:5,队列中等待执行的任务数:2,已执行完的任务数:0

                线程池中线程数:5,队列中等待执行的任务数:3,已执行完的任务数:0

                线程池中线程数:5,队列中等待执行的任务数:4,已执行完的任务数:0

                线程池中线程数:5,队列中等待执行的任务数:5,已执行完的任务数:0

                线程池中线程数:6,队列中等待执行的任务数:5,已执行完的任务数:0

               task10开始执行

                线程池中线程数:7,队列中等待执行的任务数:5,已执行完的任务数:0

               task11开始执行

                线程池中线程数:8,队列中等待执行的任务数:5,已执行完的任务数:0

               task12开始执行

               线程池中线程数:9,队列中等待执行的任务数:5,已执行完的任务数:0

               task13开始执行

              线程池中线程数:10,队列中等待执行的任务数:5,已执行完的任务数:0

               task14开始执行

               task5开始执行

               task6开始执行

               task7开始执行

               task8开始执行

               task9开始执行

       由执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程,直到线程数等于最大线程数10后,便不再创建新线程。

        如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了,因为最大线程10加上缓冲队列5,最多接受15个任务。

         在java中,并不提倡直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池,其内部也是调用ThreadPoolExecutor的构造方法,只不过参数已提前配置:

          //corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue。

          Executors.newCachedThreadPool();

          //corePoolSize和maximumPoolSize都设置为1,使用的LinkedBlockingQueue

           Executors.newSingleThreadExecutor();

           //corePoolSize和maximumPoolSize值是相等的,使用的LinkedBlockingQueue

           Executors.newFixedThreadPool(int);

          实际开发中,如能满足要求,尽量使用这三个静态方法。如ThreadPoolExecutor达不到要求,可自己继承ThreadPoolExecutor类进行重写。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容