线程总结(七):初识线程池

当我们创建线程时候,每创建一个线程,都会和操作系统进行交互,当我们需要很多线程的时候,频繁的创建线程会消耗很大的资源,为了提高性能,就需要引入“线程池”的概念了。

线程池在程序启动时就创建大量空闲线程,程序将一个 Runnable 对象传入线程池中,线程池就会分配一个线程去执行其 run 方法,当 run 方法体执行完,线程也并不会死亡,而是重新回到线程池中,等待下一个 Runnbal 对象传入。

Java 线程池框架有几个接口和实现类,他们的关系如下:

从图中可以看到 Executor、ExecutorService、ScheduledExecutorService 定义线程池接口,ThreadPoolExector 和 ScheduledThreadPoolExecutor 是线程池的实现,前者是一个普通的线程池,后者是一个定期调度的线程池,Executors 是辅助工具,用以帮助我们快速定义线程池。

  • newCachedThreadPool()

    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
  • newCachedThreadPool(ThreadFactory threadFactory)

    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。

    • threadFactory:创建新线程时使用的工厂
  • newFixedThreadPool(int nThreads)

    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

    • nThreads:池中的线程数
  • newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。

    • threadFactory:创建新线程时使用的工厂
    • nThreads:池中的线程数
  • newScheduledThreadPool(int corePoolSize)

    创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    • corePoolSize:池中所保存的线程数,即使线程是空闲的也包括在内。
  • newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

    创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    • corePoolSize:池中所保存的线程数,即使线程是空闲的也包括在内。
    • threadFactory:创建新线程时使用的工厂
  • newSingleThreadExecutor()

    创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
  • newSingleThreadExecutor(ThreadFactory threadFactory)

    创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,并在需要时使用提供的 ThreadFactory 创建新线程。

    • threadFactory:创建新线程时使用的工厂
  • newSingleThreadScheduledExecutor()

    创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
  • newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

    创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

    • threadFactory:创建新线程时使用的工厂

四种创建线程池方式的区别

newCachedThreadPool()

该方法会创建一个线程数量不定的线程池,其最大线程数为 Integer.MAX_VALUE ,这种线程池比较适合执行大量耗时较少的任务,当整个线程池处于限制状态时,线程池中的线程都会因为超时而被停止,这个时候它几乎不占用系统资源。
当传入 ThreadFactory 参数时,会使用 ThreadFactory 创建新线程。

public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPool.execute(new RunnableTest(i));
        }

        threadPool.awaitTermination(3, TimeUnit.SECONDS);
        if (!threadPool.isShutdown()) {
            System.out.println("关闭线程池");
            threadPool.shutdown();
        }
        // 这个时候再调用线程池就会出错
        // threadPool.execute(new RunnableTest(100));

    }
}

class RunnableTest implements Runnable {

    private int number;

    public RunnableTest(int number) {
        this.number = number;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getId() + " - " + number
                    + "  start");
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getId() + " - " + number
                    + "  end");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

在上面的代码中,特意将 awaitTermination 方法的时间设置为 3 秒,3 秒之后继续往下执行代码,所以执行结果是:

9 - 0  start
11 - 2  start
13 - 4  start
10 - 1  start
12 - 3  start
关闭线程池
11 - 2  end
9 - 0  end
13 - 4  end
10 - 1  end
12 - 3  end

newFixedThreadPool(int nThreads)

该方法创建一个线程数固定的线程池,当线程处于空闲状态时,它们也不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。因为线程池内线程不会被回收,所以它能更快的响应外界的线程请求。
当传入 ThreadFactory 参数时,会使用 ThreadFactory 创建新线程。

public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            threadPool.execute(new RunnableTest(i));
        }
        threadPool.shutdown();
    }
}

class RunnableTest implements Runnable {

    private int number;

    public RunnableTest(int number) {
        this.number = number;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getId() + " - " + number
                    + "  start");
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getId() + " - " + number
                    + "  end");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

因为我们设置了线程池内线程数为 3,所以执行结果如下:

9 - 0  start
10 - 1  start
11 - 2  start
10 - 1  end
9 - 0  end
11 - 2  end
9 - 3  start
11 - 4  start
9 - 3  end
11 - 4  end

newScheduledThreadPool(int corePoolSize)

该方法创建一个固定长度的线程池,返回一个 ScheduledExecutorService 对象,该对象支持定时的以及周期性的任务执行。
当传入 ThreadFactory 参数时,会使用 ThreadFactory 创建新线程。

public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService threadPool = Executors
                .newScheduledThreadPool(3);
        for (int i = 0; i < 5; i++) {
            threadPool.schedule(new RunnableTest(i), 3, TimeUnit.SECONDS);
        }
        threadPool.shutdown();
    }
}

class RunnableTest implements Runnable {

    private int number;

    public RunnableTest(int number) {
        this.number = number;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + " - " + number
                + "  start");
        System.out.println(Thread.currentThread().getId() + " - " + number
                + "  end");
    }
}

执行结果

11 - 2  start
9 - 0  start
10 - 1  start
9 - 0  end
11 - 2  end
10 - 1  end
11 - 3  start
9 - 4  start
11 - 3  end
9 - 4  end

我们执行线程时是调用了 ScheduledExecutorService 对象的 schedule 方法,并且设置延时时间为 3 秒,所以在 3 秒之后先执行了 3 个任务(因为线程池长度为 3),然后又执行剩下的 2 个任务。

newSingleThreadExecutor()

该方法可以创建一个单线程化的 Executor,即只创建唯一的工作线程来执行任务,如果这个线程发生异常导致结束,会有另一个线程取代它,保证顺序执行。
单工作线程最大的特点是可以保证顺序的执行各任务,并且在任意给定的时间不会有多个线程是活动的。

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            threadPool.execute(new RunnableTest(i));
        }

        threadPool.shutdown();
    }
}

class RunnableTest implements Runnable {

    private int str;

    public RunnableTest(int str) {
        this.str = str;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("Thread:" + Thread.currentThread().getId()
                    + " number:" + str);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

输出结果:

Thread:9 number:0
Thread:9 number:1
Thread:9 number:2
Thread:9 number:3
Thread:9 number:4

可以看到,每一次执行,都是调用了 id 是 9 的那个线程的 run 方法。

newSingleThreadScheduledExecutor()

从名字上就可以看出,这个方法是 newSingleThreadExecutor 和 newScheduledThreadPool 的结合体,这里不详述...