多线程(四)线程池

目录

一、什么是线程池

二、为什么要使用线程池

三、解读线程池 Jdk 源码

四、使用线程池执行线程任务的步骤

示例代码

五、ThreadPoolExecutor 浅析

六、使用线程池的好处


一、什么是线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

二、为什么要使用线程池

当系统创建一个新的线程,因为它涉及到与操作系统的交互,所以它的成本是比较高的。经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大 。当程序中需要使用存活周期很短的线程时,应该考虑使用线程池。

三、解读线程池 Jdk 源码

          线程池是JDK 1.5加入的,对源码翻译如下:

// 创建一个线程池,该线程池重用在一个共享的无界队列上运行的固定数量的线程。
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.  

// 在任何时候,在大多数情况下线程都是活动的处理任务。
At any point, at most {@code nThreads} threads will be active processing tasks.

// 如果在所有线程都处于活动状态时提交额外的任务,它们将在队列中等待,直到有一个线程可用为止。
If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.

// 如果任何线程在关闭之前的执行过程中由于失败而终止,那么如果需要执行后续任务,则需要一个新线程来替代它

If any thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks. 

// 池中的线程将一直存在,直到显式结束
The threads in the pool will exist until it is explicitly.

四、使用线程池执行线程任务的步骤

  • 调用 Executors 类的静态方法创建 ExecutorService 对象
  • 创建一个类,实现 Runnable 接口
  •  调用 ExecutorService 对象的 execute() 方法来执行Runnable接口实例
  • 最后当不再使用时,调用 ExecutorService 对象的 shutdown() 方法关闭线程池

Executors 是一个工厂类,主要用来创建 ExecutorService 

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

 

newFixedThreadPool() 这个静态方法用来创建一个可重用固定线程数的线程池。

示例代码

// 创建一个实现Runnable接口的类 Thread1
class Thread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 50; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + "的i值为: " + i);
            }
        }
    }
}

// 创建一个实现Runnable接口的类 Thread2
class Thread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 50; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + "的i值为: " + i);
            }
        }
    }
}

/**
 * @author zxy
 */
public class ThreadPool {

    public static void main(String[] args) {
        // 创建一个固定线程数的的线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);

        // 向线程中提交两个任务,执行指定的线程的操作。需要提供实现Runnable接口实现类的对象
        pool.execute(new Thread1());
        pool.execute(new Thread2());

        // 关闭线程池
        pool.shutdown();
    }
}


代码执行结果:

pool-1-thread-2的i值为: 1
pool-1-thread-2的i值为: 3
pool-1-thread-2的i值为: 5
pool-1-thread-2的i值为: 7
pool-1-thread-2的i值为: 9
pool-1-thread-2的i值为: 11
pool-1-thread-2的i值为: 13
pool-1-thread-2的i值为: 15
pool-1-thread-2的i值为: 17
pool-1-thread-2的i值为: 19
pool-1-thread-2的i值为: 21
pool-1-thread-2的i值为: 23
pool-1-thread-2的i值为: 25
pool-1-thread-2的i值为: 27
pool-1-thread-2的i值为: 29
pool-1-thread-2的i值为: 31
pool-1-thread-2的i值为: 33
pool-1-thread-2的i值为: 35
pool-1-thread-2的i值为: 37
pool-1-thread-2的i值为: 39
pool-1-thread-2的i值为: 41
pool-1-thread-2的i值为: 43
pool-1-thread-2的i值为: 45
pool-1-thread-2的i值为: 47
pool-1-thread-2的i值为: 49
pool-1-thread-1的i值为: 0
pool-1-thread-1的i值为: 2
pool-1-thread-1的i值为: 4
pool-1-thread-1的i值为: 6
pool-1-thread-1的i值为: 8
pool-1-thread-1的i值为: 10
pool-1-thread-1的i值为: 12
pool-1-thread-1的i值为: 14
pool-1-thread-1的i值为: 16
pool-1-thread-1的i值为: 18
pool-1-thread-1的i值为: 20
pool-1-thread-1的i值为: 22
pool-1-thread-1的i值为: 24
pool-1-thread-1的i值为: 26
pool-1-thread-1的i值为: 28
pool-1-thread-1的i值为: 30
pool-1-thread-1的i值为: 32
pool-1-thread-1的i值为: 34
pool-1-thread-1的i值为: 36
pool-1-thread-1的i值为: 38
pool-1-thread-1的i值为: 40
pool-1-thread-1的i值为: 42
pool-1-thread-1的i值为: 44
pool-1-thread-1的i值为: 46
pool-1-thread-1的i值为: 48
pool-1-thread-1的i值为: 50

 

五、ThreadPoolExecutor 浅析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

创建一个线程池时的参数: 

  • corePoolSize 表示核心池的大小。

当提交一个任务到线程池的时候,线程池将会创建一个线程来执行任务。这个值的大小设置非常关键,设置过小将频繁地创建或销毁线程,设置过大则会造成资源浪费。

线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。

  • maximumPoolSize 表示线程池的最大数量。

如果队列 (workQueue) 满了,并且已创建的线程数 小于 maximumPoolSize,则线程池会进行创建新的线程执行任务。
如果 等待执行的线程数 大于 maximumPoolSize,缓存在队列中;
如果 maximumPoolSize 等于 corePoolSize ,即是固定大小线程池。

  • keepAliveTime 表示线程活动的保持时间

当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用;

当一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉

  • TimeUnit 表示线程活动保持时间的单位 

它的单位有:TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS

  • workQueue 表示线程池中存放被提交但尚未被执行的任务的队列 

维护着等待执行的 Runnable 对象。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。

  • threadFactory 用于设置创建线程的工厂

它用来给每个创建出来的线程设置一个名字,就可以知道线程任务是由哪个线程工厂产生的。

  • handler  表示拒绝处理策略

线程数量大于最大线程数,当超过workQueue的任务缓存区上限的时候,就可以调用该策略,这是一种简单的限流保护。

从JDK 源码里面可以看到:

其中里面其设置的 corePoolSize(核心池的大小)maximumPoolSize(最大线程数) 都是 nThreads,其设定的阻塞队列是无界的,则饱和策略将失效,所有请求将一直排队等待被执行,可能会产生内存溢出的风险。因此阿里巴约编码规范不推荐用Executors来创建ThreadPoolExecutor。

使用Executors创建线程池时要明确创建的阻塞队列是否有界。因此最好自己创建ThreadPoolExecutor。

 

 ThreadPoolExecutor pool1 = (ThreadPoolExecutor) pool;
 // 设置核心池的大小
 pool1.setCorePoolSize(15);
 // setkeepAliveTime()方法 设置线程没有任务时最多保持多长时间后会停止
 pool1.setKeepAliveTime(60000, TimeUnit.HOURS);

 

六、使用线程池的好处

  • 降低资源消耗 重复利用线程池中已经创建好的线程,不需要每次都创建,降低创建和销毁造成的消耗。
  • 提高响应的速度。当任务分配下来时,任务无需等到创建线程就能被执行,减少了创建线程的时间。
  • 方便进行线程管理。线程无限制的被创建,会占用系统资源并且还会降低系统的稳定性。使用线程池可以进行统一管理,设置核心池的大小,设置线程没有任务时最多保持多长时间。

 

发布了89 篇原创文章 · 获赞 2175 · 访问量 32万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览