JUC学习之线程池

一、简介

首先,我们先了解一下为什么要用线程池?

很多年以前,单核CPU的时候,多线程其实是假的,只是线程之间高速切换造成的“多线程”假象。现如今,基本上都是多核CPU电脑,多个线程各自跑在独立的CPU上,不用切换,效率比较高。

线程池的主要特点:

  • 线程可复用;
  • 控制最大并发数;
  • 管理线程;

线程池的优势:

线程池主要是控制运行的线程数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。总结主要有一下四点:

  • 降低系统消耗,通过重复利用已创建好的线程减低线程创建和销毁带来的开销;
  • 提高响应速度,当任务到达时,任务可以不需要等待线程创建就可以执行;
  • 提高线程的可管理性,线程是稀缺资源,如果无限的创建,不仅会消耗很多系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控;

二、线程池相关类说明

线程池整体架构图大概如下:

线程池的使用涉及三个比较重要的类:

【a】Executor接口:执行已提交的可运行任务的对象。该接口提供了一种将任务提交与如何运行每个任务的机制(包括线程使用、调度等细节)分离的方法。

其中包含一个重要方法:

void

execute(Runnable command)

在将来的某个时候执行给定的命令

【b】ExecutorService接口:

public interface ExecutorService
extends Executor

ExecutorService继承自Executor接口:用于管理终止的方法的执行程序,以及可以产生用于跟踪一个或多个异步任务进展的未来的方法。ExecutorService可以被关闭,这将导致它拒绝新任务。提供了两种不同的方法来关闭ExecutorService。

  • shutdown()方法允许在终止之前执行以前提交的任务;
  • shutdownNow()方法可以防止等待的任务启动并尝试停止当前正在执行的任务;

在终止时,执行程序没有正在执行的任务,没有等待执行的任务,也不能提交新的任务。应该关闭未使用的ExecutorService,以允许回收其资源。

Executor.execute(Runnable)方法创建并返回一个可用于取消执行和/或等待完成的Future。方法invokeAny和invokeAll执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部任务完成。

ExecutorService重要方法:

方法返回值类型

方法描述

boolean

awaitTermination(long timeout, TimeUnit unit)

阻塞,直到所有任务在关闭请求后完成执行,或超时发生,或当前线程被中断(以先发生的情况为准)

<T> List<Future<T>>

invokeAll(Collection<? extends Callable<T>> tasks)

执行给定的任务,返回一个结果列表,其中包含所有任务完成时的状态和结果

<T> List<Future<T>>

invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

执行给定的任务,当所有任务完成或超时结束时,返回包含状态和结果的期货列表(以先发生的情况为准)

<T> T

invokeAny(Collection<? extends Callable<T>> tasks)

执行给定的任务,返回一个已成功完成的任务的结果(即,不抛出异常),如果有的话

<T> T

invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

执行给定的任务,返回一个已成功完成的任务的结果(即,而不抛出异常),如果在给定超时超时之前有任何操作

boolean

isShutdown()

如果此执行程序已关闭,则返回true

boolean

isTerminated()

如果所有任务在关闭后都已完成,则返回true

void

shutdown()

启动有序关闭,在此过程中执行以前提交的任务,但不接受任何新任务

List<Runnable>

shutdownNow()

尝试停止所有正在执行的任务,停止对等待的任务的处理,并返回等待执行的任务列表

<T> Future<T>

submit(Callable<T> task)

提交一个返回值的任务以供执行,并返回一个表示该任务的未决结果的Future

Future<?>

submit(Runnable task)

提交一个可运行任务以供执行,并返回一个表示该任务的Future

<T> Future<T>

submit(Runnable task, T result)

提交一个可运行任务以供执行,并返回一个表示该任务的Future

官网使用示例:

class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
 }

【c】Executors工具类:

public class Executors
extends Object

Executors其实是一个工具类,继承自Object基类。这个包中定义的用于Executor、ExecutorService、ScheduledExecutorService、ThreadFactory和可调用类的工厂和实用程序方法:

  • 创建并返回使用常用配置设置设置的ExecutorService;
  • 创建并返回使用常用配置设置设置的ScheduledExecutorService;
  • 创建并返回一个ThreadFactory,它将新创建的线程设置为已知状态;

常用方法:

方法返回值类型

方法描述

static ExecutorService

newCachedThreadPool()

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

static ExecutorService

newCachedThreadPool(ThreadFactory threadFactory)

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

static ExecutorService

newFixedThreadPool(int nThreads)

创建一个线程池,该线程池重用在共享无界队列上操作的固定数量的线程

static ExecutorService

newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

创建一个线程池,该线程池重用在共享无界队列上操作的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程

static ScheduledExecutorService

newScheduledThreadPool(int corePoolSize)

创建一个线程池,该线程池可以调度命令在给定延迟后运行,或定期执行

static ScheduledExecutorService

newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

创建一个线程池,该线程池可以调度命令在给定延迟后运行,或定期执行

static ExecutorService

newSingleThreadExecutor()

创建一个执行程序,该执行程序使用一个工作线程在一个无界队列上操作

static ExecutorService

newSingleThreadExecutor(ThreadFactory threadFactory)

创建一个执行程序,该执行程序使用一个工作线程操作一个无界队列,并在需要时使用提供的ThreadFactory创建一个新线程

static ScheduledExecutorService

newSingleThreadScheduledExecutor()

创建一个单线程执行器,该执行器可以调度命令在给定延迟后运行,或定期执行

static ScheduledExecutorService

newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

创建一个单线程执行器,该执行器可以调度命令在给定延迟后运行,或定期执行

static ExecutorService

newWorkStealingPool()

使用所有可用的处理器作为其目标并行度级别,创建一个窃取工作的线程池

static ExecutorService

newWorkStealingPool(int parallelism)

创建一个线程池,该线程池维护足够的线程以支持给定的并行度级别,并且可以使用多个队列来减少争用

三、线程池种类

Executor主要提供了五大类线程池:

  • newFixedThreadPool()方法:该方法返回一个固定数量的线程池;
  • newSingleThreadExecutor()方法:该方法返回只有一个线程的线程池;
  • newCachedThreadPool()方法:该方法返回一个可根据实际情况调整线程数量的线程池;
  • newScheduledThreadPool()方法:该方法返回一个ScheduleExecutorService对象,可以指定线程数量;
  • newSingleThreadScheduledExecutor()方法:该方法返回一个ScheduleExecutorService对象,线程数量为1;

下面通过简单的示例说明各种线程池的使用方法:

【a】 Executors.newFixedThreadPool(5): 创建固定数量的线程池

public class T14_ThreadPool {

    public static void main(String[] args) {
        /**
         * 创建一个固定大小为5个线程的线程池
         */
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try {
            for (int i = 0; i < 10; i++) {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "---使用线程--");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

运行结果:

pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-2---使用线程--
pool-1-thread-4---使用线程--
pool-1-thread-3---使用线程--
pool-1-thread-5---使用线程--

我们发现,由于指定了线程池中只有五个线程,所以五个线程一共要完成十个任务,必然有一些线程要干多一点事情。

【b】Executors.newSingleThreadExecutor(): 创建只有一个线程的线程池

public class T14_ThreadPool {

    public static void main(String[] args) {
        /**
         * 创建一个只有一个线程的线程池
         */
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try {
            for (int i = 0; i < 10; i++) {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "---使用线程--");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

运行结果:

pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--

我们发现,由于指定了线程池中只有一个线程,十个任务全部都由同一个线程来完成。

【c】Executors.newCachedThreadPool(): 创建动态扩充线程数量的线程池

public class T14_ThreadPool {

    public static void main(String[] args) {
        /**
         * 创建一个支持缓存的线程池
         * 可扩容
         */
        ExecutorService executorService = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "---使用线程--");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

运行结果:

pool-1-thread-1---使用线程--
pool-1-thread-2---使用线程--
pool-1-thread-9---使用线程--
pool-1-thread-8---使用线程--
pool-1-thread-7---使用线程--
pool-1-thread-10---使用线程--
pool-1-thread-6---使用线程--
pool-1-thread-5---使用线程--
pool-1-thread-4---使用线程--
pool-1-thread-3---使用线程--

我们发现,由于此类线程池可动态扩展,根据实际情况,具体会生成多少个线程我们也不能确定,所以如果需要的线程不能预估,那么此种方式需要谨慎使用。

【d】Executors.newScheduledThreadPool(10):创建支持定时调度的线程池 

public class T14_ThreadPool {

    public static void main(String[] args) {
        /**
         * 计划任务
         */
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "---使用线程--"), 0, 1, TimeUnit.SECONDS);
    }
}

运行结果: 

pool-1-thread-1---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-2---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-3---使用线程--
pool-1-thread-2---使用线程--
pool-1-thread-4---使用线程--
pool-1-thread-1---使用线程--
pool-1-thread-5---使用线程--
pool-1-thread-3---使用线程--
....

 下面再来看一个通过ScheduledFuture.get()获取线程执行结果的示例:

public class T13_TestScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 3; i++) {
            ScheduledFuture<String> scheduledFuture = scheduledExecutorService.schedule(() -> {
                System.out.println(Thread.currentThread().getName());
                return UUID.randomUUID().toString();
            }, 1, TimeUnit.SECONDS);

            String s = null;
            try {
                s = scheduledFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println(s);
        }
        scheduledExecutorService.shutdown();
    }
}

运行结果:

pool-1-thread-1
87150c0b-7353-413f-9b4e-62e1e4f64bc1
pool-1-thread-1
53d0a8ec-89f8-4076-9301-740b54fc8cfd
pool-1-thread-2
e1df93c9-73c1-45aa-a902-1679c3cf4804

由此可见,通过scheduleAtFixedRate方法可以定时执行某个任务。

/**
 * 计划任务
 * @param 任务执行的具体命令
 * @param 延迟首次执行的时间
 * @param 期间连续执行的时期
 * @param 单位时间单位的初始延迟和周期参数
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit);

四、总结

本文主要总结线程池的一些优势、使用方法以及常用API,并结合示例说明了各种线程池的使用。下一篇文章将会总结线程池相关底层原理和自定义线程池。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值