为什么要使用线程池:目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。 传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处 于不停的创建线程,销毁线程的状态
线程池:是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务
线程池底层分析:复用机制,使用BlockingQueue阻塞队列缓存线程。为什么使用阻塞队列,因为其他线程执行时需要时间的,在其他线程执行的时候,新线程需要等待,但是不能丢失,缓存到阻塞队列里。阻塞队列通过阻塞可以保留住当前想要继续入队的任务。当队列中没有线程的时候,去阻塞队列中去拿,会被阻塞,直到队列中有新的线程。
java中的线程池是通过Executor框架实现的,Executor 框架包括类:Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable和Future,FutureTask等。
线程池的优点:
1,可以减少线程的创建和销毁的开销,性能佳。
2,提高响应的时间,当任务到达的时候(当前线程池中已经存在线程)任务可以不需要线程创建,就能执行。
3,统一分配和销毁,有规则的对创建的线程进行管理。
线程池的缺点:
在不适合的场合下可能会比不使用线程池消耗更多的资源。
Executor: 所有线程池的接口,只有一个方法。Executor用于执行提交的Runnable任务。Executor提供了每个线程如何运行,线程的具体使用调度机制的解耦的一种方式。一般用Executor执行线程,而不是显示地创建线程
public interface Executor {
void execute(Runnable command);
}
ExecutorService: 增加Executor的行为,是Executor实现类的最直接接口。
Executors: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
线程池的分类以及创建:
有四种线程池:可缓存的,定长的,定时的,单例的。
四种线程池的创建都是对ThreadPoolExecutor的构造的包装。
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
- corePoolSize 核心线程数,实际运行线程数
- maximumPoolSize最大线程数,最多可以创建多少线程。
- keepAliveTime (非核心线程)闲置线程最大存活时间。
- unit 超时时间的单位 有七种取值 (TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒) - BlockingQueue 阻塞队列,是java.util.concurrent下的主要用来控制线程同步的工具。如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。
- RejectedExecutionHandler handler 超出 maximumPoolSizes + workQueue 时,任务会交给RejectedExecutionHandler来处理
Executors.newCachedThreadPool(); 创建一个无界的可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
Executors.newFixedThreadPool(int nThreads)创建一个固定大小的线程池,控制线程最大并发数,超出的线程会在队列中等待
Executors.newScheduledThreadPool(int corePoolSize)创建一个定长线程池,支持定时及周期性任务执行。
Executors.newSingleThreadExecutor() 创建一个单线程的后台线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newCachedThreadPool
public class CacheThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
final int count = i;
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": i = " + count );
}
});
}
}
}
执行的结果显示:并不是申请要创建100个线程,就会执行100个线程。会有线程会进行缓存,然后进行复用。
可缓存的线程池:他是一个‘伪无界线程池’
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
他创建了一个核心线程为0,可创建最大线程数为整型量最大值,当非核心线程60秒之内没有需要处理的线程,则被回收。
此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。SynchronousQueue是一个是缓冲区为1的阻塞队列(同步移交)。
要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。因此即便SynchronousQueue一开始为空且大小为1,第一个任务也无法放入其中,因为没有线程在等待从SynchronousQueue中取走元素。因此第一个任务到达时便会创建一个新线程执行该任务。
使用情况:对于大量短暂异步任务的程序来说,使用该线程池能够大大提高性能 。但是对于耗时的任务,阻塞队列长度为1,他的最大线程数是Integer.MAX_VALUE,所以当大量任务呗提交以后,会一直创建新的线程,消耗更多的资源。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这是一个定长的线程池,构造的时候,传入参数,该参数即是核心线程数,也是最大线程数,并且空闲线程永远都不会被回收,他的阻塞队列是一个LinkedBlockingQueue,队列长度是整型量的最大值。所以该线程池可控制线程最大并发数。
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
参数与上边定长线程池一致,但是缓存队列他用的是DelayedWorkQueue,这是一个变长的队列,初始长度为16,每次增加自己的一半(newCapacity = oldCapacity + (oldCapacity >> 1)))是一个无界队列,它能按一定的顺序对工作队列中的元素进行排列。
特点:指定延时后执行任务。周期性重复执行任务。
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(3);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("输出" + System.currentTimeMillis());
}
}, 0, 4, TimeUnit.SECONDS);
该线程是表明,创建3个个核心线程,并且提交的线程按照,0表示第一次执行时的延迟时间,4表示每经过4秒执行一次任务(指定延迟时间。重复执行);并且提交多个线程的时候,按一定的顺序执行。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
LinkedBlockingQueue<Runnable>()也是一个"伪无界队列"。
这是一个单例的线程池,核心线程数和最大线程数都为1,也就是说,该线程池最大的并发量就是1,使用唯一的线程来保证所有请求都是按照先进先出的顺序来执行的。在线程执行的过程中,如果发生为异常,线程结束,会产生新的线程来执行后续的任务。并且在线程结束当前任务之后可以重用,而不会销毁。(在这里单例的线程池和我们普通的线程十分相似,而什么时候该起一个Thread,什么时候该使用单例的线程池呢。1,当线程所要执行的任务已经确定 2,线程不需要频繁的创建和销毁 3, 线程不需要被复用的时候,可以使用Thread)
RejectedExecutionHandler (拒绝策略)
线程池中的拒绝策略有四种:拒绝策略是在(有界)队列满了,并且实际运行线程数大于最大可创建线程数时,线程池拒绝执行任务时的策略。
AbortPolicy (中止策略) 默认策略:表示拒绝执行,抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
DiscardPolicy(抛弃策略):表示如果现在已经超过了最大线程数,则抛弃新提交的任务。而不做任何其他的事
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
DiscardOldestPolicy(丢弃最老的):该策略表示,抛弃最老的哪个线程,也就是队列的头部的线程,腾出位置,将新提交的线程加到队列。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
CallerRunsPolicy(调用者线程去执行):该策略表示,如果现在线程池已经不能执行时,该任务将由调用者线程去执行。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}