目录
为什么要使用线程池
从Java虚拟机的角度来看,操作系统分配给每个进程的内存是有限的,比如:假设分配给Java进程限制为mGB(m是个定值),虚拟机提供了参数来控制Java堆和方法区的这两个部分内存的最大值。剩余的内存为mGB-Xmx(最大堆容量)-MaxPermSize(最大方法区容量),程序计数器消耗内存很小(忽略不计),如果虚拟机进程本身耗费的内存不计算在内。那么大致的计算公式为
虚拟机栈+本地方法栈 ≈ mGB(操作系统分配给Java进程的内存)-Xmx(最大堆容量)-MaxPermSize(最大方法区容量)【m是个定值】
由此公式可见,假设每个线程分配的栈容量不变,随着建立的线程数量的增多,会导致内存溢出(OOM)。
所以减少线程创建的数量,使已经创建的线程能够复用则是一种多线程编码中常见的手段。
线程池的定义和特点
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行.
特点:
- 线程复用:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 控制最大并发数:提过响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
- 管理线程:提高线程的客观理想。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
线程池的实现
jdk提供的线程池实现
-
Executors.newFixedThreadPool(int)
创建一个定长线程池,可控制线程最大并发数,超出的线程回在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
-
Executors.newSingleThreadExecutor()
创建一个单线程的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
-
Executors.newCachedThreadPool()
执行很多短期异步的小程序或负载较轻的服务器
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当县城空闲超过60s,就销毁线程
线程池底层实现的7大重要参数
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后,就会把到达的任务放到缓存队列当中
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间
- 当前线程池数量超过corePoolSize时,档口空闲时间达到keepAliveTime值时,多余空闲线程会被销毁到只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runable的策略
线程池的底层原理
-
在创建了线程池之后,等待提交过来的 任务请求。
-
当调用execute()方法添加一个请求任务时,线程池会做出如下判断。
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
- 如果此时队列满了且正在运行的线程数小于maximumPoolSize,那么还是要创建非核心线程立刻运行此任务;
- 如果队列满了且正在运行的线程数量大于或等于maxmumPoolSize,那么线程池会启动饱和拒绝策略来执行;
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉;所以线程池的所有任务完成后他最大会收缩到corePoolSize的大小。
拒绝策略
jdk内置的四种拒绝策略如下:
- AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。这样不会丢弃任务,但是,任务提交线程的性能有可能急剧下降。
- DiscardOldestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。
以上策略均实现了RejectedExecutionHandler接口。
public interface RejectedExecutionHandler {
/**
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
自定义线程池和拒绝策略
自定义一个线程池,5个核心线程,最大线程数也是5,拥有一个只有10个容量的等待队列(如果任务量极大,很可能把内存“撑死”)。自定义拒绝策略(不抛出异常,因为任务提交端没有进行异常处理的话,则有可能是整个系统崩溃,我们将任务丢弃的信息进行打印)
package test16;
public class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId());
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
package test16;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"is discard");
}
});
for (int i = 0; i < Integer.MAX_VALUE; i++) {
es.submit(myTask);
Thread.sleep(10);
}
}
}
自定义线程创建:ThreadFactory
线程池中的线程时怎么创建的,当线程池需要创建线程时,就会调用这个方法。
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
使用自定义线程池可以让我们更加自由的设置线程池中所有线程的状态,下面的案例使用自定义的ThreadFactory,一方面记录了线程的创建,另一方面将所有的线程都设置为守护线程,这样,当主线程退出后,将会强制销毁线程池。
package test17;
public class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId());
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
package test17;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
ExecutorService es = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
System.out.println("create"+thread);
System.out.println(thread.getId());
return thread;
}
});
for(int i=0;i<5;i++){
es.submit(myTask);
}
Thread.sleep(2000);
}
}