美文网首页架构师+大数据
在并发编程中线程池的使用

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

作者: A尚学堂Nancy老师 | 来源:发表于2018-12-05 15:56 被阅读10次

一、为什么要使用线程池

当需要处理的任务较少时,我们可以自己创建线程去处理,但在高并发场景下,我们需要处理的任务数量很多,由于创建销毁线程开销很大,这样频繁创建线程就会大大降低系统的效率。

此时,我们就可以使用线程池,线程池中的线程执行完一个任务后可以复用,并不被销毁。合理使用线程池有以下几点好处:

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, BlockingQueue<Runnable> workQueue);

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

BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

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

BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

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

BlockingQueue<Runnable> workQueue,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<Runnable>(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类进行重写。

本文为原创作品,转载请注明出处!

相关文章

  • 线程

    Java 并发编程:线程池的使用 Java 并发编程:线程池的使用java 多线程核心技术梳理 (附源码) 本文对...

  • Java并发编程:线程池的使用

    Java并发编程:线程池的使用

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

    在并发编程中线程池的使用 一、为什么要使用线程池 当需要处理的任务较少时,我们可以自己创建线程去处理,但在高并发...

  • Java高并发 -- 线程池

    Java高并发 -- 线程池 主要是学习慕课网实战视频《Java并发编程入门与高并发面试》的笔记 在使用线程池后,...

  • Java线程池分析

    引言 在并发编程中,我们经常会使用到线程池,当然我们也可以手动一个一个创建线程,那么为何我们还是推崇大家使用线程池...

  • 多线程 | 4.线程池

    Java并发编程:线程池的使用 线程池基础 请求队列 线程池维护一定数量的线程,当线程池在运行状态的线程数量达上...

  • Java线程池

    Java并发编程:线程池的使用 1.使用线程池的优点?1.降低资源消耗:重复利用线程池中的线程节省线程创建和销毁带...

  • java并发

    1.并发编程中的锁 并发编程中的各种锁java高并发锁的3种实现Java并发机制及锁的实现原理 2.线程池核心线程...

  • 线程池的几个灵魂拷问(二)

    线程池虽然在并发编程里很强大,但线程池使用面临的核心的问题在于:线程池的参数并不好配置。一方面线程池的运行机制不是...

  • 多线程(五)——线程池原理剖析&锁的深度化

    什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在...

网友评论

    本文标题:在并发编程中线程池的使用

    本文链接:https://www.haomeiwen.com/subject/ecjmfqtx.html