线程池

1、什么是线程池

java.util.concurrent.Executors 提供了一个 java.util.concurrent.Executor 接 口的实现用于创建线程池

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器

单元的闲置时间,增加处理器单元的吞吐能力。 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执 行任务的时间,T3 销毁线程时间。

如果:T1 + T3 进大于 T2,则可以采用线程池,以提高服务器性能。

一个线程池包括以下四个基本组成部分: 1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销 毁线程池,添加新任务; 2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以 循环的执行任务; 3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行, 它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等; 4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注如何缩短戒调整 T1,T3 时间的技术,从而提高服务器程序性 能的。它把 T1,T3 分别安排在服务器程序的启劢和结束的时间段戒者一些空闲 的时间段,这样在服务器程序处理客户请求时,不会有 T1,T3 的开销了。

线程池不仅调整 T1,T3 产生的时间段,而且它还显著减少了创建线程的数目,

 

2.常见线程池

1newSingleThreadExecutor 单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务 

2.1 newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线 程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线 程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

 

2newFixedThreadExecutor(n) 固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量, 然后后面迚入等待队列直到前面的任务完成才继续执行 

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程

池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为 执行异常而结束,那么线程池会补充一个新线程。

3newCacheThreadExecutor(推荐使用) 可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空 闲(一般是 60 秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程 池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线 程池大小完全依赖于操作系统(戒者说 JVM)能够创建的最大线程大小。

 4newScheduleThreadExecutor 大小无限制的线程池,支持定时和周期性的执行线程

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
 

1:FixedThreadPool 和 SingleThreadPool:
允许的请求队列(底层实现是 LinkedBlockingQueue)长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM 2:CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导

致 OOM。

java四种线程池的使用

 

参考:https://blog.csdn.net/w05980598/article/details/79425071

参考:https://blog.csdn.net/achuo/article/details/80623893

 

一、四种线程池 

Java通过Executors提供四种线程池,分别为

  1. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  2. newFixedThreadPool  创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 
  4. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 

二、核心类

四种线程池本质都是创建ThreadPoolExecutor类,ThreadPoolExecutor构造参数如下

  1. int corePoolSize, 核心线程大小

  2. int maximumPoolSize,最大线程大小

  3. long keepAliveTime, 超过corePoolSize的线程多久不活动被销毁时间

  4. TimeUnit unit,时间单位

  5. BlockingQueue<Runnable> workQueue 任务队列

  6. ThreadFactory threadFactory 线程池工厂

  7. RejectedExecutionHandler handler 拒绝策略

三、阻塞队列

  1. ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列

  2. LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列(常用)

  3. PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列

  4. DelayQueue: 一个使用优先级队列实现的无界阻塞队列

  5. SynchronousQueue: 一个不存储元素的阻塞队列(常用)

  6. LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列

  7. LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列

四、四种默认线程池源码初步分析

  • 复制代码
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
    }
    
     public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
    }
    
    //创建一个定长线程池,支持定时及周期性任务执行。延迟执行
    public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
    }
    复制代码
  • 查看源码,四种线程池都是创建了ThreadPollExecutor对象,只是传递的参数不一样而已,观察传入的workQueue 都是默认,即最大可添加Integer.MAX_VALUE个任务,这也就是阿里巴巴java开发规范禁止直接使用java提供的默认线程池的原因了

五、线程池任务执行流程

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务

  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程

  6. 当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

六、使用场景详解

newCachedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;时间单位TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
  • 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定时间,则该线程会被销毁。
  • 适用:执行很多短期的异步任务
  • 复制代码
    /**
     * 1.创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程<br>
     * 2.当任务数增加时,此线程池又可以智能的添加新线程来处理任务<br>
     * 3.此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小<br>
     */
    public static void cacheThreadPool() {
            ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
            for (int i = 1; i <= 10; i++) {
                final int ii = i;
                try {
                    Thread.sleep(ii * 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                cachedThreadPool.execute(()->out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii));
            }
    }
    -----output------
    线程名称:pool-1-thread-1,执行1
    线程名称:pool-1-thread-1,执行2
    线程名称:pool-1-thread-1,执行3
    线程名称:pool-1-thread-1,执行4
    线程名称:pool-1-thread-1,执行5
    线程名称:pool-1-thread-1,执行6
    线程名称:pool-1-thread-1,执行7
    线程名称:pool-1-thread-1,执行8
    线程名称:pool-1-thread-1,执行9
    线程名称:pool-1-thread-1,执行10
    复制代码

 

newFixedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量n,corePoolSize和maximumPoolSize均为n;keepAliveTime为0L;时间单位TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
  • 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:执行长期任务
  • 复制代码
     /**
      * 1.创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小<br>
      * 2.线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程<br>
      * 3.因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字,和线程名称<br>
      */
    public static void fixTheadPoolTest() {
            ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
            for (int i = 0; i < 10; i++) {
                final int ii = i;
                fixedThreadPool.execute(() -> {
                    out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
    }
    ------output-------
    线程名称:pool-1-thread-3,执行2
    线程名称:pool-1-thread-1,执行0
    线程名称:pool-1-thread-2,执行3
    线程名称:pool-1-thread-3,执行4
    线程名称:pool-1-thread-1,执行5
    线程名称:pool-1-thread-2,执行6
    线程名称:pool-1-thread-3,执行7
    线程名称:pool-1-thread-1,执行8
    线程名称:pool-1-thread-3,执行9
    复制代码

newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;时间单位TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
  • 通俗:创建只有一个线程的线程池,当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:按顺序执行任务的场景
  • 复制代码
    /** 
     *创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
     */
    public static void singleTheadPoolTest() {
            ExecutorService pool = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 10; i++) {
                final int ii = i;
                pool.execute(() -> out.println(Thread.currentThread().getName() + "=>" + ii));
            }
    }
    
    -----output-------

     线程名称:pool-1-thread-1,执行0
     线程名称:pool-1-thread-1,执行1
     线程名称:pool-1-thread-1,执行2
     线程名称:pool-1-thread-1,执行3
     线程名称:pool-1-thread-1,执行4
     线程名称:pool-1-thread-1,执行5
     线程名称:pool-1-thread-1,执行6
     线程名称:pool-1-thread-1,执行7
     线程名称:pool-1-thread-1,执行8
     线程名称:pool-1-thread-1,执行9

    复制代码

NewScheduledThreadPool:

  • 底层:创建ScheduledThreadPoolExecutor实例,该对象继承了ThreadPoolExecutor,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;时间单位TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:执行周期性任务
  • 复制代码
    /**
     * 创建一个定长线程池,支持定时及周期性任务执行。延迟执行
     */
    public static void sceduleThreadPool() {
            ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
            Runnable r1 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:3秒后执行");
            scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS);
            Runnable r2 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:延迟2秒后每3秒执行一次");
            scheduledThreadPool.scheduleAtFixedRate(r2, 2, 3, TimeUnit.SECONDS);
            Runnable r3 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:普通任务");
            for (int i = 0; i < 5; i++) {
                scheduledThreadPool.execute(r3);
            }
    }
    ----output------
    线程名称:pool-1-thread-1,执行:普通任务
    线程名称:pool-1-thread-5,执行:普通任务
    线程名称:pool-1-thread-4,执行:普通任务
    线程名称:pool-1-thread-3,执行:普通任务
    线程名称:pool-1-thread-2,执行:普通任务
    线程名称:pool-1-thread-1,执行:延迟2秒后每3秒执行一次
    线程名称:pool-1-thread-5,执行:3秒后执行
    线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
    线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
    线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
    线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
    复制代码

七、备注:

  • 一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为其大小为Integer.MAX_VALUE),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数corePoolSize来创建线程,也就是说线程池大小被忽略了。
  • 如果线程池任务队列采用ArrayBlockingQueue队列,初始化设置了最大队列数。那么ThreadPoolExecutor的maximumPoolSize才会生效,那么ThreadPoolExecutor的maximumPoolSize才会生效会采用新的算法处理任务,
  • 例如假定线程池的最小线程数为4,最大为8,ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即核心线程数)直到队列完全填满,也就是说等待状态的任务小于等于10,ThreadPoolExecutor也只会利用4个核心线程线程处理任务。
  • 如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。如果线程数已经等于最大线程数,任务队列也已经满了,则线程池会拒绝这个任务,默认拒绝策略是抛出异常。
  • 这个算法背的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;这时第二个节流阀—最大线程数就起作用了。

 

 

站长统计

登录注册写文章

首页下载APP

 

抽奖

Java线程池详解

小红军storm关注赞赏支持

Java线程池详解

小红军storm关注

62019.03.02 20:29:01字数 2,008阅读 144,331

1、线程池的优势

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。

2、线程池的主要参数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

3、线程池流程

线程池流程

1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则。
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。
3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。

(1、判断线程池中当前线程数是否大于核心线程数,如果小于,在创建一个新的线程来执行任务,如果大于则
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。
3、判断线程池中当前线程数是否大于最大线程数,如果小于,则创建一个新的线程来执行任务,如果大于,则执行饱和策略。)

4、线程池为什么需要使用(阻塞)队列?

回到了非线程池缺点中的第3点:
1、因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。

另外回到了非线程池缺点中的第1点:
2、创建线程池的消耗较高。
或者下面这个网上并不高明的回答:
2、线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。

5、线程池为什么要使用阻塞队列而不使用非阻塞队列?

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。
使得在线程不至于一直占用cpu资源。

(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下
while (task != null || (task = getTask()) != null) {})。

不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢?

6、如何配置线程池

CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

7、java中提供的线程池

Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor

java线程池对比

1、newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)

2、newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)

3、newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。

4、newScheduledThreadPool:适用于执行延时或者周期性任务。

8、execute()和submit()方法

1、execute(),执行一个任务,没有返回值。
2、submit(),提交一个线程任务,有返回值。
submit(Callable<T> task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。

submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。

Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。

https://www.cnblogs.com/dolphin0520/p/3949310.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值