本文共 3073 字,大约阅读时间需要 10 分钟。
以web服务器为例, 其实在低并发量的时候,我觉的不使用线程池也没有太大关系。但是一旦并发量上来了,或者有人对你的网站发起恶意流量攻击,
那么假如我们单纯地对于每个请求都创建一个线程来处理、会怎么样呢?
首先java是如何创建线程的呢?
1、继承Thread类
2、实现Runnable接口,然后再new Thread(Runnable command)
也就是先创建一个线程实例,然后去调用实例的start()方法,这个start方法内部会调用本地方法start0(),也就是执行start0, java虚拟机会调用本地操作系统的创建线程接口,创建一个操作系统线程,这个线程约占内存几MB,我们先算它为1MB,那么对于一个4GB内存的机器,也仅仅只能容纳4000个线程,而且实际上在Linux中, 所有线程的内核栈以及thread_info描述符所占用的内存默认不能超过物理内存的1/8。
所以在达到一定的并发级别时, 如果采用这种简单的对于每个请求创建一个线程进行处理,那么必然会导致CPU有大量的时间用于创建线程,导致其他处理任务无法及时得到调度,超时。对于用户的体验就是网站打不开,因为任务在后台都超时了。
顾名思义就是把线程放在一个池子里;在Java中用一个类ThreadPoolExecutor来实现。这个池就是ThreadPoolExecutor的一个成员变量HashSet实现的。
/** * Set containing all worker threads in pool. Accessed only when * holding mainLock. */ private final HashSetworkers = new HashSet ();
介绍线程池的两个重要参数
1、corePoolSize: 在线程池中(实际上存在ThreadPoolExecutor的hashSet中)的worker数(一个worker就对应一个线程)小于等于corePoolSize, 那么对应新来的请求就会去创建一个线程。
2、maximumPoolSize:对于这个参数有多种策略,我们暂时大体认为这样:线程池中的线程数绝对不能超过maximumPoolSize。
如果想要使用线程池,当然需要先new一个,构造参数我们先不管
1 |
|
想要使用一个线程池执行一个任务, 调用execute()方法, 这个command就是一个任务task
1 |
|
那么这个execute()具体干了啥呢?它大体会干三件事, 这里可以整体上描述一下,具体可查看源码
1、如果线程池的hashSet中的worker数(线程数)小于corePoolSize,直接创建新增一个worker(addWorker()方法)来运行这个任务
2、如果大于corePoolSize, 如果线程池处于运行状态,就把task塞进线程池的队列中,等待其他worker空闲的时候再去该队列中取出任务来执行
这里出现了一个新的数据结构: workQueue
3、其他情况会拒绝掉该task(这个先带过,后面再讲)
private final BlockingQueueworkQueue;
下面我们再来看看这个addWorker()方法:就是给线程池新建一个worker(线程)来执行任务,这个方法干了哪些事呢?
1、进入一个循环检查线程池状态、现有worker数量等来判断是不是真的应该去新建一个线程,如果不应该, return false,表示新建失败
2、如果确实应该新建一个worker,那就调用addWorker(firstTask),以该task为参数构建一个worker, 然后调用worker成员变量thread.start()
worker是Worker类的实例,Worker类是ThreadPoolExecutor的内部final类。 看一下这个Worker类的构建方法
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
看第三句:worker内部的成员变量thread,它是用worker实例作为参数调用newThread()构建的线程实例,Worker类 implement Runnable,如此一来, 执行thread的start()方法、便会新建一个线程去执行worker的run()。
接下来再看看worker的run()方法干了啥, 这个方法很简单
/** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); }
那么我们再看看这个runWorker()方法干了啥
这个方法其实就是进入一个循环,这个循环干嘛呢?它先看看有没有firstTask, 就是每个worker创建之初需要执行的那个任务;如果这个初始任务执行完了, 它就会检查队列中有没有其他任务,如果有的话、取出队列中一个任务执行该task的run()方法、会在本线程内执行,这样就实现了使用一个线程去执行多个任务的功能。当然,task仅仅是实现了Runnable接口,并没有调用new Thread(Runnable command)去构建一个线程实例,所以task也没有start()可以调用。如果firstTask执行完了, 队列中没有其他任务了,那么该worker就寿终正寝,线程将会死亡,当然在死亡之前,会进行一些清理工作和登记工作,比如登记这个worker完成了多少task,汇总到线程池的总完成任务数中。
拒绝策略:
1、AbortPolicy:直接抛出异常, 阻止系统正常运行。
2、CallerRunPolicy: 只要线程池没有关闭, 该策略直接在调用者线程中, 运行当前被丢弃的任务, 显然这样做不会真的丢弃任务, 但是, 任务提交线程的性能极有可能会急剧下降。
3、DiscardOldestPolicy: 丢弃最老的一个请求, 也就是即将被执行的一个任务, 并尝试再次提交当前任务。
4、DiscardPolicy: 该策略默默地丢弃无法处理的任务, 不予任何处理。如果允许任务丢失, 这是最好的一种方案。
转载地址:http://lnzji.baihongyu.com/