10.1 为什么使用线程池(Why Thread Pools?)
线程池在java多线程开发中用的非常普遍,它里面存放了一定数量的Thread,当有任务需要执行的时候,(通常是Runnable对象),就从线程池里面拿出空闲的Thread来执行任务,执行完了之后再把Thread放回去等待下一个任务。
使用线程池的理由如下
1)创建一个Thread的开销是很大的,使用线程池可以省去这部分的开销,对有些对时间敏感的工程来说,是必须的
2)第二个原因是方便我们的程序设计,如果你有太多的线程需要你去维护,这是一个很繁琐的工程,使用线程池可以你管理,让你把精力投入到具体的逻辑当中
3)第三个原因是线程池可以让多个线程并发运行
这里需要说明一下并发和并行的区别,并发就好比一个人同时吃三个馒头,并行相当于三个人吃三个馒头,专业一点就是,并发是一个CPU运行多个任务,并行是多个CPU运行多个任务
为了方便理解,我模拟了一个线程池
package com.yellow; import java.util.LinkedList; /** * 线程池的模拟 * * @author yellowbaby * */ public class ThreadPool extends ThreadGroup { /** * 用来存放Task的工作队列 */ private LinkedList<Runnable> workQueue = new LinkedList<Runnable>(); public ThreadPool(int poolSize) { super("");// 父类无参数的构造函数是private的 startWorkThreads(poolSize); } /** * 开启工作线程 * @param poolSize */ private void startWorkThreads(int poolSize) { for (int i = 0; i < poolSize; i++) { new WorkThread(i).start(); } } /** * 执行任务 * @param task */ public synchronized void execute(Runnable task) { if (task != null) { workQueue.add(task);//往工作队列里面添加一个任务 notify();//唤醒一个正在等待的工作线程 } } /** * 工作线程从工作队列里面取task,如果工作队列为空,就等待 */ private synchronized Runnable getTask() { while (workQueue.size() == 0) { try { System.out.println("工作线程 " + Thread.currentThread().getName() + " 正在等待任务"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return workQueue.removeFirst(); } /** * 工作线程,从队列里面取Task,取到了就执行,没有就等待 */ private class WorkThread extends Thread { public WorkThread(int id) { super(ThreadPool.this, id + "");// 将当前线程加入当前ThreadGroup,并且把线程重命名 } @Override public void run() { while (!interrupted()) { Runnable task = getTask(); if (task == null) return; task.run(); } } } /** * 测试类 * @param args */ public static void main(String[] args) { ThreadPool threadPool = new ThreadPool(3); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 10; i++) { threadPool.execute(createTask(i)); } } public static Runnable createTask(final int id) { return new Runnable() { @Override public void run() { System.out.println("工作线程 " + Thread.currentThread().getName() + " 正在执行Task" + id); } }; } }
这是执行结果
工作线程 0 正在等待任务 工作线程 1 正在等待任务 工作线程 2 正在等待任务 工作线程 0 正在执行Task0 工作线程 1 正在执行Task2 工作线程 2 正在执行Task1 工作线程 2 正在执行Task5 工作线程 2 正在执行Task6 工作线程 2 正在执行Task7 工作线程 2 正在执行Task8 工作线程 2 正在执行Task9 工作线程 2 正在等待任务 工作线程 1 正在执行Task4 工作线程 1 正在等待任务 工作线程 0 正在执行Task3 工作线程 0 正在等待任务
可以看出,线程池在初始化的时候就启动了3个线程,然后一直处于等待任务的状态,任务加入后,3个线程并发执行,当所有的任务都完成后,工作线程又处于等待状态
现在来看看如果我们使用JDK里面的线程池应该怎么写
package com.yellow; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池的模拟 * * @author yellowbaby * */ public class ThreadPool { /** * 测试类 * @param args */ public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3);//创建一个固定有三个工作线程的线程池 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 10; i++) { threadPool.execute(createTask(i)); } } public static Runnable createTask(final int id) { return new Runnable() { @Override public void run() { System.out.println("工作线程 " + Thread.currentThread().getName() + " 正在执行Task" + id); } }; } }