在java编程中,多线程是解决并发的重要手段,然而使用多线程同时创建和销毁线程需要消耗资源,无限制的创建线程就会导致资源耗尽,系统崩溃。因此,当服务器内存和CPU不是绝对够用时,采用线程池对多线程进行管理是合理的手段。线程池的好处(引用其他文章):
降低资源消耗,重复利用现有的线程降低线程的创建和销毁对资源的消耗;
提高响应速率,当任务到达时,不用再等待线程的创建,利用现有线程即可立即执行任务;
提高线程的可管理性,线程是稀缺资源,如果无限制的创建线程,不仅会消耗系统资源,而且会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
Java中线程池所有的类位于rt.jar中java.util.concurrent包中,线程池核心类是
ThreadPoolExecutor。下面是该类是使用示例:
package thread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
ThreadPoolExecutor threadPool =
new ThreadPoolExecutor(5, 5, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
Thread thread1 = new Thread(new MyRunnable(),"thread1");
Thread thread2 = new Thread(new MyRunnable(),"thread2");
Thread thread3 = new Thread(new MyRunnable(),"thread3");
Thread thread4 = new Thread(new MyRunnable(),"thread4");
Thread thread5 = new Thread(new MyRunnable(),"thread5");
threadPool.execute(thread1);
threadPool.execute(thread2);
threadPool.execute(thread3);
threadPool.execute(thread4);
threadPool.execute(thread5);
} catch (IllegalArgumentException e) {
// TODO: handle exception
e.printStackTrace();
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(2000);
System.out.println("PP:"+Thread.currentThread().getName()+":ggg");
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
运行结果:
PP:pool-1-thread-2:ggg
PP:pool-1-thread-5:ggg
PP:pool-1-thread-1:ggg
PP:pool-1-thread-3:ggg
PP:pool-1-thread-4:ggg
在上面例子中使用ThreadPoolExecutor创建了可以并发执行5个线程的线程池,ThreadPoolExecutor类是线程池的核心类,继承了AbstractExecutorService类,AbstractExecutorService类实现了ExecutorService接口,在concurrent包中线程池相关的类图:
ThreadPoolExecutor类构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
构造函数需要5个参数:
corePoolSize:核心线程池大小,当线程池小于corePoolSize时,对于新的任务就会创建新线程执行,当线程池数大小超过corePoolSize时,新提交的任务被放入workQueue中;
maximumPoolSize:最大线程池大小,线程池中并发执行的线程的最大数量,当maximumPoolSize大于corePoolSize时,且workQueue已满时,新的任务就会创建新的线程执行;
keepAliveTime:线程池中超过corePoolSize大小的空闲线程存活时间;
unit:keepAliveTime单位;
workQueue:任务队列。
但是在实际开发中,并不建议使用ThreadPoolExecutor构造函数创建线程池,java给出了一个Executors类,Executors类提供了几个静态方法,用来创建线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
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 ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
四种方法:
newFixedThreadPool:是创建固定大小的线程池,核心线程数量与最大线程数量一致;
newSingleThreadExecutor:是创建只能并发执行一个线程的线程池;
newScheduledThreadPool:创建一个可缓存的的线程池,并支持定时及周期性任务执行。
newCachedThreadPool:是创建缓存线程池,若线程池长度超过任务处理需求,则回收线程,否则创建新线程执行任务;
newFixedThreadPool、newSingleThreadExecutor方法比较好理解,都是创建固定大小的线程池;
newScheduledThreadPool比Timer更安全,功能更强大,示例:
//延迟3秒执行任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
//延迟2秒后,每隔3秒执行一次
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("delay 2 seconds and rate 3 seconds");
}
}, 2, 3, TimeUnit.SECONDS);
newCachedThreadPool方法的最大线程池数量是Integer.MAX_VALUE,所有当并发任务量大时,线程池长度变大,有可能造成内存不足,系统崩溃,例如引用其他作者遇到的情况:使用无限大小线程池 newCachedThreadPool 可能遇到的问题
因此,创建线程池建议使用newFixedThreadPool,但是如何确定线程池的大小呢?需要结合服务器资源、业务需求等综合考虑。查看其他作者的文章,线程池大小的确定要考虑应用系统的类型,应用系统类型一般分为计算密集型和IO密集型。
计算密集型的应用线程池大小设置公式为:
线程数 = cpu核数+1
IO密集型应用线程池大小设置公式为:
公式1:线程数=cpu核数/(1-阻塞系数); 其中阻塞系统取值为0.8-0.9,参考;
公式2:线程数=cpu核数*2+1 ;是否可行待确认;
公式3:线程数=((线程等待数据+线程cpu时间)/线程cpu时间)*cpu核数;当服务器只部署这一个应用并且只要这一个线程池,这估算可能合理,待确认。