基本认识
以下摘自百度百科
线程池的概念:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
线程池的作用:在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些”池化资源”技术产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的。
总而言之:线程池就是系统通过池化资源的概念,达到节省资源的一种手段。
关于线程与任务:《Java变成思想第四版》里面描述得很清楚,一言概之:线程是用来驱动任务的。初学时以为一个线程就是一个任务,其实不然。
Java线程池
Java有四种常见的线程池,在java.util.concurrernt.Executors类里面以静态工厂形式返回:
- newCachedThreadPool:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。(Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.)
- newFixedThreadPool: 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。(Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.)
- newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。( Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.)
- newSingleThreadExecutor: 创建一个使用单个线程的线程池,以无界队列方式来运行该线程。(Creates an Executor that uses a single worker thread operating off an unbounded queue.)
观察这几个静态工厂,对于newCachedThreadPool、newSingleThreadExecutor,API客户端直接调用即可;而对于newFixedThreadPool,客户端需要关注参数:int nThreads;newScheduledThreadPool,客户端需要关注参数:int corePoolSize。
于是我们来看看源码里面这些参数都代表什么意思
以创建CachedThreadPool为例:
ExecutorService ctp = Executors.newCachedThreadPool();
newCachedThreadPool()
方法的源码如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
我们直接来到ThreadPoolExecutor类,看下几个构造器的参数都代表什么意思。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
由于暂时只为入门的基础认识,所以后面几个参数我们不关心,姑且认为他们是线程池底层操作相关的参数,我们只看前面四个甚至只看前面两个API客户端会使用的参数。
源码文件的截图:
- corePoolSize:常驻线程池的线程数
- maximunPoolSize:线程池中允许的最多线程数
- keepAliveTime:当现有线程数多于常驻的数目时,空闲线程若在这个最大时长还没等待到新任务,则被终止。
- unit:keepAliveTimed的时间单位
再来看一下创建几种线程池时前面几个参数默认的都是啥:
1、Cached线程池:可以看到常驻的数目为0,最大的线程数目可以达到很大,当线程空闲时60秒内没有新任务则终止。从名字就可以看出这个效果了。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2、Fixed线程池:可以看到常驻的数目由客户端决定,最大的线程数目同样也是,当线程空闲时则马上被终止。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3、Single线程池:只有也只能有一条工作线程,没有任务就终止。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4、Scheduled线程池:常驻线程数由客户端决定
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
简单实例
public class Test {
private static ExecutorService cached = Executors.newCachedThreadPool();
private static ExecutorService fixed = Executors.newFixedThreadPool(3);
private static FileWriter fw;
static {
try {
fw = new FileWriter("j:/log.txt");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable sellingApple = new Selling("apple");
Runnable sellingBanana = new Selling("banana");
for (int i = 0; i < 5; i++) {
cached.execute(sellingApple);
}
cached.execute(sellingBanana);
}
private static class Selling implements Runnable {
private int count = 500;
private String item;
public Selling(String item) {
this.item = item;
}
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (this) {
if (count < 1) {
break;
}
sell();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
cached.shutdown();
}
private void sell() {
try {
fw.write(Thread.currentThread().getName() + " sold " + item
+ ": #" + count-- + "\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结果:我们总共在缓存线程池里面开启了6条线程,前面5条是用来驱动卖苹果这个任务,最后的第6条用来卖香蕉。
假设把上面主程序的cached线程池换成参数为3的fixed线程池,结果如下:为什么必须是3条线程卖完苹果后才能卖香蕉呢?我们开了5条线程去卖苹果,和1条线程去卖香蕉,但是fixed线程池最多只能有3条线程,所以卖香蕉这个任务在队列里面等待着。等这3条线程卖完全部苹果后,就会复用其中1条线程去卖香蕉了。如果我们把参数改为6,则和上述结果一样。
小结:不同线程池作用不同,execute开启一条线程去驱动一个任务,shutdown()关闭线程池回收资源,但任务还是会按队列顺序执行完(Initiates an orderly shutdown in which previously submitted tasks are executed),当前线程数不够驱动任务时,任务就在队列等着。