• 线程池 —— 使用介绍


    引入线程池的原因

    通常我们需要使用线程去完成某项任务的时候都会去创建一个线程,一般都会这么写:

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            // TODO
        }
    });
    thread.start();
    

    这样操作直接且简单,当然是没有错的,但是却存在这一些问题。在应付一些线程并发不多的情况时是完全够用的,但是如果并发的线程数量很多,就会造成系统的效率降低。主要会造成如下影响:

    • 频繁创建和销毁线程占用大量不必要的系统处理时间,影响性能。
    • 频繁创建和销毁线程容易导致 GC 的频繁执行,造成内存抖动,导致移动设备出现卡顿。
    • 大量的线程并发非常消耗内存,容易造成 OOM 问题。
    • 不利于扩展,如定时执行、周期执行、线程中断。

    而解决上面问题的方法,就是引入线程池的概念。线程池使得线程可以重复利用,执行完任务后并不会销毁线程,而是继续执行其他的任务。这样可以有效的减少并控制创建线程的数量,防止并发线程过多,内存消耗过度,从而提高系统的性能。

    同时线程池还可以很方便的控制线程的并发数量,线程的定时任务,单线程顺序执行等等。

    ExecutorService 接口

    ExecutorService 就是一般所说的线程池接口,它继承 Executor 接口,同时还提供了一些管理终止的方法,以及可以跟踪一个或者多个异步任务线程执行状况并生成 Future 的方法。

    而真正意义上的线程池是 ThreadPoolExecutor,它实现了 ExecutorService 接口,并且封装了一系列接口使其具有线程池的特性。

    线程池:ThreadPoolExecutor

    查看源码后我们发现 ThreadPoolExecutor 有四个构造方法,都是调用其中一个构造方法进行初始化操作。具体代码如下:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) { ... }
    
    参数 作用
    corePoolSize 线程池中的核心线程数量
    maximumPoolSize 线程池中的最大线程数量
    keepAliveTime 超过核心线程数量的多余空闲线程,在超过 keepAliveTime 时间内没有任务,则被销毁
    unit keepAliveTime 的时间单位
    workQueue 任务队列,用来存储已经提交但没有被执行的任务,不同的线程池采取的排队策略各不相同
    threadFactory 线程工厂,用来创建线程池中的线程
    handler 当最大线程数和任务队列已经饱和,而导致无法接受新的任务时,用来拒绝接受任务的策略

    五种不同功能的线程池

    可以看出想要创建一个 ThreadPoolExecutor 对象并不容易,所以一般推荐使用工厂类 Executors 的工厂方法来创建线程池对象。Executors 主要提供了下面五种不同功能的线程池:

    1. 固定型线程池 newFixedThreadPool

    创建一个固定线程数量的线程池。每次提交一个任务就创建一个线程,直到达到设定的线程数量,之后线程池中的线程数量不再变化。当有新任务提交时,如果有空闲的线程,则由该空闲线程处理任务,否则将任务存至任务队列中,一旦有线程空闲下来,则按照 FIFO 的方式处理队列中的任务。该线程池适合一些稳定的正规并发线程,多用于服务器。

    定义:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    

    运行实例:

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 1; i <= 10; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println("Thread: " + threadName + ", running Task" + index);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    

    运行结果:

    02-24 08:15:20 I/System.out: Thread: pool-1-thread-1, running Task1
    02-24 08:15:20 I/System.out: Thread: pool-1-thread-2, running Task2
    02-24 08:15:20 I/System.out: Thread: pool-1-thread-3, running Task3
    02-24 08:15:23 I/System.out: Thread: pool-1-thread-1, running Task4
    02-24 08:15:23 I/System.out: Thread: pool-1-thread-2, running Task5
    02-24 08:15:23 I/System.out: Thread: pool-1-thread-3, running Task6
    02-24 08:15:26 I/System.out: Thread: pool-1-thread-1, running Task7
    02-24 08:15:26 I/System.out: Thread: pool-1-thread-2, running Task8
    02-24 08:15:26 I/System.out: Thread: pool-1-thread-3, running Task9
    02-24 08:15:29 I/System.out: Thread: pool-1-thread-1, running Task10
    

    观察线程名发现一共只创建了 3 个线程处理任务,当所有线程都处于运行状态时,再提交的任务则会进入等待,3 个线程处理完之前任务后会被等待队列中的任务复用,所以观察时间发现,每次都是 3 个任务同时运行,间隔 3 秒后再运行后面 3 个任务,任务执行的顺序即提交的顺序。

    2. 缓存型线程池 newCachedThreadPool

    创建一个可以根据实际情况调整线程数量的线程池。线程池中的线程数量不确定,当需要时会创建新的线程,当有线程空闲时复用空闲线程。通常对执行大量短暂异步任务的程序,可以提升其效率。

    当然,线程池中的线程并不会越来越多,每个线程都有一个参数用来设置保持活动的时间,一旦线程空闲时间超过了该时间,则立即销毁该线程,默认的保持活动时间为 60 秒,我们可以在其定义中看到这个默认参数。

    定义:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    运行实例:

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= 10; i++) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        final int index = i;
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println("Thread: " + threadName + ", running Task" + index);
                try {
                    Thread.sleep(index * 500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    

    运行结果:

    02-24 08:25:29 I/System.out: Thread: pool-1-thread-1, running Task1
    02-24 08:25:30 I/System.out: Thread: pool-1-thread-1, running Task2
    02-24 08:25:31 I/System.out: Thread: pool-1-thread-2, running Task3
    02-24 08:25:32 I/System.out: Thread: pool-1-thread-1, running Task4
    02-24 08:25:33 I/System.out: Thread: pool-1-thread-2, running Task5
    02-24 08:25:34 I/System.out: Thread: pool-1-thread-1, running Task6
    02-24 08:25:35 I/System.out: Thread: pool-1-thread-3, running Task7
    02-24 08:25:36 I/System.out: Thread: pool-1-thread-2, running Task8
    02-24 08:25:37 I/System.out: Thread: pool-1-thread-1, running Task9
    02-24 08:25:38 I/System.out: Thread: pool-1-thread-4, running Task10
    

    我们让任务间隔 1 秒提交一次,并且每个任务的时间逐渐增长,从结果可以发现,每隔 1 秒就会有一个任务被执行,刚开始一个线程可以处理过来,但随着任务时间的增长,线程池创建了新的线程来处理那些提交的任务,当有之前的线程处理完后,也会被赋予任务执行。最后一共创建了 4 个线程。

    3. 单例线程 newSingleThreadExecutor

    创建一个只含有一个线程的线程池。每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,当这个线程空闲时,再按照 FIFO 的方式顺序执行队列中的任务。

    定义:

    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    

    运行实例:

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 1; i <= 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println("Thread: " + threadName + ", running Task" + index);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    

    运行结果:

    02-24 09:00:18 I/System.out: Thread: pool-1-thread-1, running Task1
    02-24 09:00:19 I/System.out: Thread: pool-1-thread-1, running Task2
    02-24 09:00:20 I/System.out: Thread: pool-1-thread-1, running Task3
    02-24 09:00:21 I/System.out: Thread: pool-1-thread-1, running Task4
    02-24 09:00:22 I/System.out: Thread: pool-1-thread-1, running Task5
    02-24 09:00:23 I/System.out: Thread: pool-1-thread-1, running Task6
    02-24 09:00:24 I/System.out: Thread: pool-1-thread-1, running Task7
    02-24 09:00:25 I/System.out: Thread: pool-1-thread-1, running Task8
    02-24 09:00:26 I/System.out: Thread: pool-1-thread-1, running Task9
    02-24 09:00:27 I/System.out: Thread: pool-1-thread-1, running Task10
    

    结果显而易见,从始至终只有一个线程在执行,该线程顺序执行已提交的线程。

    4. 调度型线程池 newScheduledThreadPool

    创建一个可指定大小的,可以调度线程根据 schedule 延迟执行,或者周期执行的线程池。

    运行实例:

    System.out.println("Start Task");
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
    for (int i = 1; i <= 10; i++) {
        final int index = i;
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println("Thread: " + threadName + ", running Task" + index);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 3, TimeUnit.SECONDS);
    }
    

    运行结果:

    02-24 09:47:45 I/System.out: Start Task
    02-24 09:47:48 I/System.out: Thread: pool-1-thread-1, running Task1
    02-24 09:47:48 I/System.out: Thread: pool-1-thread-2, running Task2
    02-24 09:47:48 I/System.out: Thread: pool-1-thread-3, running Task3
    02-24 09:47:50 I/System.out: Thread: pool-1-thread-1, running Task5
    02-24 09:47:50 I/System.out: Thread: pool-1-thread-2, running Task4
    02-24 09:47:50 I/System.out: Thread: pool-1-thread-3, running Task6
    02-24 09:47:52 I/System.out: Thread: pool-1-thread-1, running Task7
    02-24 09:47:52 I/System.out: Thread: pool-1-thread-3, running Task8
    02-24 09:47:52 I/System.out: Thread: pool-1-thread-2, running Task9
    02-24 09:47:54 I/System.out: Thread: pool-1-thread-1, running Task10
    

    运行情况基本和 newFixedThreadPool 类似,但是在开始运行时,有 3 秒钟的延迟。

    5. 调度型单例线程 newSingleThreadScheduledExecutor

    创建一个只含有一个线程,可以调度线程根据 schedule 延迟执行,或者周期执行的线程池。

    运行实例:

    System.out.println("Start Task");
    ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
    singleThreadScheduledExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println("Thread: " + threadName + ", running Task1");
        }
    }, 1, 2, TimeUnit.SECONDS);
    

    运行结果:

    02-24 10:01:36 I/System.out: Start Task
    02-24 10:01:37 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:39 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:41 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:43 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:45 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:47 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:01:49 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:02:51 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:02:53 I/System.out: Thread: pool-1-thread-1, running Task
    02-24 10:02:55 I/System.out: Thread: pool-1-thread-1, running Task
    

    运行情况基本和 newSingleThreadExecutor 类似,但是在开始运行时,有 1 秒钟的延迟,并且每隔 2 秒周期性的执行一次任务。

    自定义线程池

    如果仔细观察上面五种线程池的定义就会有所发现,其实线程池的功能不同,和其内部的 BlockingQueue 不同有关。如果我们想要实现一些不同功能的自定义线程池,可以从其中的 BlockingQueue 着手。

    比如现在有一种 BlockingQueue 的实现类是 PriorityBlockingQueue,他可以实现队列按照优先级排序,那我们就可以利用它实现一个按任务的优先级来处理任务的线程池。

    首先创建一个实现了 Runnable 与 Comparable 接口的抽象类 PriorityRunnable,用来存放任务。实现了 Comparable 接口就可以进行优先级的比较。

    public abstract class PriorityRunnable implements Runnable, Comparable<PriorityRunnable> {
        private int mPriority;
    
        public PriorityRunnable(int priority) {
            if (priority < 0 ) {
                throw new IllegalArgumentException();
            }
            mPriority = priority;
        }
    
        @Override
        public int compareTo(PriorityRunnable runnable) {
            int otherPriority = runnable.getPriority();
            if (mPriority < otherPriority) {
                return 1;
            } else if (mPriority > otherPriority) {
                return -1;
            } else {
                return 0;
            }
        }
    
        public int getPriority() {
            return mPriority;
        }
    }
    

    接着就可以创建一个基于 PriorityBlockingQueue 实现的线程池,其核心数量定义为 3,方便测试结果。然后创建 10 个优先级依次增加的 PriorityRunnable 任务,提交给线程池执行。

    ExecutorService threadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>());
    for (int i = 1; i <= 10; i++) {
        final int priority = i;
        threadPool.execute(new PriorityRunnable(priority) {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                System.out.println("Thread: " + name + ", running PriorityTask" + priority);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    

    运行结果:

    02-25 11:34:45 I/System.out: Thread: pool-1-thread-1, running PriorityTask1
    02-25 11:34:45 I/System.out: Thread: pool-1-thread-2, running PriorityTask2
    02-25 11:34:45 I/System.out: Thread: pool-1-thread-3, running PriorityTask3
    02-25 11:34:47 I/System.out: Thread: pool-1-thread-1, running PriorityTask10
    02-25 11:34:47 I/System.out: Thread: pool-1-thread-2, running PriorityTask9
    02-25 11:34:47 I/System.out: Thread: pool-1-thread-3, running PriorityTask8
    02-25 11:34:49 I/System.out: Thread: pool-1-thread-1, running PriorityTask7
    02-25 11:34:49 I/System.out: Thread: pool-1-thread-2, running PriorityTask6
    02-25 11:34:49 I/System.out: Thread: pool-1-thread-3, running PriorityTask5
    02-25 11:34:51 I/System.out: Thread: pool-1-thread-1, running PriorityTask4
    

    可以看到,运行结果和 newFixedThreadPool 非常相似,唯一不同就在于等待任务不是按照 FIFO 的方式执行,而是优先级较高的任务先执行。

    线程池的优化

    线程池可以自定义内部线程的数量,定义的数量影响着系统的性能表现,如果定义过大会浪费资源,定义过小线程并发量太低,处理速度也变低了,所以合理的设置线程数量是很重要的。通常需要考虑 CPU 的核心数、内存的大小、并发请求的数量等因素。

    通常核心线程数量可以设为 CPU 核心数 + 1,最大线程数量可以设为 CPU 核心数 * 2 + 1。

    获取 CPU 核心数的方法为:

    Runtime.getRuntime().availableProcessors();
    

    线程池的调用方式

    以下方法都可以调用线程池执行,但是效果不同,可以参考文档或源码了解:

    • void execute(Runnable command);
    • Future<?> submit(Runnable task);
    • <T> Future<T> submit(Runnable task, T result);
    • <T> Future<T> submit(Callable<T> task);
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks);
    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

    线程池的关闭

    void shutdown();

    该方法不再接受新的任务提交,在终止线程池之前,允许执行完以前提交的任务。

    List<Runnable> shutdownNow();

    该方法不再接受新的任务提交,并且把任务队列中的任务直接移除,尝试停止正在执行的任务。返回等待的任务列表。

  • 相关阅读:
    未来中国最热门的十大职业排行榜
    中国金融牌照18种(内附各牌照注册条件)
    现有的一些人脸数据库
    广信科教集团
    省部级干部list
    解读Google分布式锁服务
    数学算法那些事
    细数二十世纪最伟大的十大算法
    链接分析算法之:HillTop算法
    Regex Failure
  • 原文地址:https://www.cnblogs.com/theo/p/6442845.html
Copyright © 2020-2023  润新知