线程池(Thread Pool):
一种线程的使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这是未来避免在处理短时间任务时创建与销毁线程的代价。
Executor框架是JavaSE5中引入的,其内部使用了线程池机制。在java.util.concurrent包中。Executorz在客户端和任务执行之间提供了一个间接层,与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而不需显式地管理线程的生命周期。
Executor 框架包括线程池、Executor、Executors、ExecutorService、Future、Callable等。
线程池的优点:
•降低系统资源消耗,通过重用已经存在的线程,降低线程创建和销毁造成的消耗;
•提高系统响应速度,当有任务到达时,无需等待新线程的创建便可立即执行;
•方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源, 更是占用过多资源而阻塞系统,从而降低系统的稳定性。线程池能有效管控线程, 统一分配,调优,提供资源使用率。
•线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
ThreadPoolExecutor的默认构造函数:
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 队列没满时,线程最大并发数
- maximumPoolSize 队列满后线程能达到的最大并发数
- keepAliveTime 空闲线程过多久被回收的时间限制
- unit keepAliveTime的时间单位
- workQueue 阻塞的队列类型
handler 超出maximumPoolSize+ workQueue时,任务会交给handler来处理
corePoolSize,maximumPoolSize,workQueue之间关系(此部分转载自.cnblogs.com/linguanh/p/8000063.html)
•当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
•当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
•当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
•当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理。
•当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
•当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。
ThreadPoolExecutor的使用:
•提交任务:可以通过execute和submit两种方式向线程池提交一个任务,execute方法没有返回值,submit会返回一个future,可以通过future来判断任务是否执行成功。
•线程池关闭:shutDown()和shutDownNow()。shutDown是将线程池状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutDownNow是将线程池的状态设置为STOP状态,然后终端所有任务,包括正在执行的线程,并返回等待执行任务的列表。
•中断采用interrupt
代码示例:
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ExecutorService service = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
//第一种方式:execute(Runnable)
service.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread()+"execute方式");
}
});
//第二种方式:submit(Callable)
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread()+"submit方式");
return 2;
}
});
try {
Integer number = future.get();
System.out.println("返回结果:"+number);
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i = 0;i<10;i++){
service.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread()+"execute方式");
}
});
}
//关闭线程池,并返回等待执行任务的列表
List<Runnable> list = service.shutdownNow();
System.out.println("打印等待执行任务的列表");
for (Runnable runnable : list) {
System.out.println(runnable);
}
}
}
实验结果:
Thread[pool-1-thread-1,5,main]execute方式
Thread[pool-1-thread-2,5,main]submit方式
返回结果:2
Thread[pool-1-thread-3,5,main]execute方式
Thread[pool-1-thread-4,5,main]execute方式
Thread[pool-1-thread-5,5,main]execute方式
Thread[pool-1-thread-1,5,main]execute方式
Thread[pool-1-thread-2,5,main]execute方式
打印等待执行任务的列表
线程池.ThreadPoolExecutorTest$3@51d92803
线程池.ThreadPoolExecutorTest$3@7d206f0
线程池.ThreadPoolExecutorTest$3@6dc57a92
线程池.ThreadPoolExecutorTest$3@3ff23f8b
线程池.ThreadPoolExecutorTest$3@3929df79
四种线程池类:
1. newCachedThreadPool
核心线程数为0,最大线程数为Integer.MAX_VALUE。当线程池中的线程都处于活动状态的时候,线程池就会创建一个新的线程来处理任务。该线程池中的线程超时时长为60秒,所以当线程处于闲置状态超过60秒的时候便会被回收。
import java.util.concurrent.*;
public class NewCachedThreadPoolDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0;i<5;i++){
service.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread());
}
});
}
//关闭线程池
service.shutdown();
}
}
实验结果:
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-2,5,main]
2.newFixedThreadPool
一种线程数量固定的线程池。在这个线程池中所容纳最大的线程数就是创建时设置的核心线程数。如果线程池的线程处于空闲状态的话,它们并不会被回收,除非是这个线程池被关闭。如果所有的线程都处于活动状态的话,新任务就会处于等待状态,直到有线程空闲出来。
import java.util.concurrent.*;
public class NewFixedThreadPoolDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 0;i<10;i++){
service.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread());
}
});
}
//关闭线程池
service.shutdown();
}
}
实验结果:
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-2,5,main]
3.newSingleThreadExecutor
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。这对希望在另一个线程中连续运行的任何事物来说,是很有用的,例如:监听进入的套接字连接任务,他对于希望在线程中运行短任务也同样方便,例如,更新本地货远程日志的小任务,或者事件是分发线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewSingleThreadExecutorDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i = 0;i<10;i++){
service.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread());
}
});
}
//关闭线程池
service.shutdown();
}
}
实验结果:
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
4.newScheduledThreadPool
它的核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收。
—edule(Runnable command, long delay, TimeUnit unit):延迟一定时间后执行Runnable任务;
—edule(Callable callable, long delay, TimeUnit unit):延迟一定时间后执行Callable任务;
—eduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延迟一定时间后,以间隔period时间的频率周期性地执行任务;
—eduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):与scheduleAtFixedRate()方法很类似,但是不同的scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolDemo {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.schedule(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("延迟1秒后打印");
}
}, 1000, TimeUnit.MILLISECONDS);//延迟1秒后执行任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("延迟1秒后以2秒的频率执行任务-》1");
}
}, 1000, 2000, TimeUnit.MILLISECONDS);//延迟1秒后以2秒的频率执行任务(这里的2秒指的是上次任务开始到这次任务开始)
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("延迟1秒后以3秒的频率执行任务-》2");
}
}, 1000, 3000, TimeUnit.MILLISECONDS);//延迟1秒后以3秒的频率执行任务(这里的3秒指的是上次任务结束到这次任务开始)
Thread.sleep(10000);
service.shutdown();
}
}
实验结果:
延迟1秒后打印
延迟1秒后以2秒的频率执行任务-》1
延迟1秒后以3秒的频率执行任务-》2
延迟1秒后以2秒的频率执行任务-》1
延迟1秒后以3秒的频率执行任务-》2
延迟1秒后以2秒的频率执行任务-》1
延迟1秒后以2秒的频率执行任务-》1
延迟1秒后以3秒的频率执行任务-》2
延迟1秒后以2秒的频率执行任务-》1
线程池的使用注意事项
- 线程池的大小,多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身特点决定线程池的大小,一般来说,如果代码结构合理的话,线程数量和CPU数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如果有必要可采用自适应算法来动态调整线程池的大小,以提高CPU的有效利用率和系统的整体性能。
- 并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
- 线程泄露。这是线程应用中的一个严重问题,当任务执行完毕而线程没能但会池中就会发生线程泄露现象。
(以上文字部分来源于网络和课本、JDK文档)