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);
            }
        };
    }
}