Java:线程池

参考:Android开发——Android中常见的4种线程池(保证你能看懂并理解)

参考:干货,谈谈对java线程池的理解(面试必备)
参考:线程池,这一篇或许就够了

为什么要用线程池?

使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用,减少创建和销毁线程的性能开销。其次,能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。最后,线程池能够对线程进行管理,比如使用ScheduledThreadPool来设置延迟N秒后执行任务,并且每隔M秒循环执行一次。

ThreadPoolExecutor

如何创建线程池?

在 Java 中,新建一个线程池对象非常简单,Java 本身提供了工具类java.util.concurrent.Executors,可以使用如下代码创建一个固定数量线程的线程池:

ExecutorService service = Executors.newFixedThreadPool(10);

这样就简单的创建了保护10个线程的线程池。我们来看下内部源码:

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

可以看到是通过ThreadPoolExecutor类来实现创建的。

ThreadPoolExecutor参数解析

我们来看看创建一个ThreadPoolExecutor类需要传入那些参数,各个参数的意义是什么。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    ...
}
  • corePoolSize:核心运行线程个数

核心运行线程个数:核心线程会在线程池中一直存在,即使这些线程在空闲状态啥事都不做也不会将它们回收掉,当然前提是你没有设置 allowCoreThreadTimeOut 为 true。

  • maximumPoolSize:最大线程个数
    最大线程个数:用以限制线程池中线程的数量,当大于了这个值就会将准备新加的异步任务由一个丢弃处理机制来处理。如果你自己构造了 pool 且传入了一个 无限 的 队列 且没有设置它的 容量,那么不好意思,最大线程数会永远 <= corePoolSize,maximumPoolSize 变成了无效的。
  • keepAliveTime:非核心线程闲置超时时长
    默认是 0,当线程没有任务处理后空闲线程保持多长时间,不推荐使用
  • unit:时间单位
  • workQueue:任务等待队列
    该线程池中的任务队列:维护着等待执行的 Runnable 对象。
    常用的workQueue类型:
  1. SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

  2. LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

  3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

  4. DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

  • threadFactory:线程创建工厂
    是构造 Thread 的方法,一个接口类,可以使用默认的 default实现,也可以自己去包装和传递,主要实现 newThread 方法即可;

  • defaultHandler
    当参数 maximumPoolSize 达到后丢弃处理的方法实现,java 提供了 5种丢弃处理的方法,当然也可以自己弄,主要是要实现接口 RejectedExecutionHandler 中rejectedExecution(Runnabler, ThreadPoolExecutor e) 方法,java 默认使用的是AbortPolicy,他的作用是当出现这种情况的时候抛出一个异常;通常得到线程池后会调用其中的 submit 或 execute 方法去提交执行异步任务,其实 submit 方法最终会调用execute 方法来进行操作,只是他提供了一个 Future
    来托管返回值的处理而已,当你调用需要有返回值的信息时用它来处理是比较好的,这个 Future 会包装 Callable 信息。

线程池运行

package org.godotengine.interview.java;

import androidx.annotation.NonNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author bob
 * Date     19-9-19
 * Description
 */
public class ThreadPoolTest {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2;
    private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue<>(1024);
    public static Executor THREAD_POOL_MY;
    //一共执行20个任务 ,核心线程数是4,最大核心线程数是10,目前加入的runnable20个(相当于20个任务),
    //20个任务需要执行,但是核心线程数只有4个,还有16个任务,由于LinkedBlockingQueue队列是最大存放的任务为10   个,队列满了,则会创建新的线程去执行任务,这个时候最大线程是10, 非核心线LinkedBlockingQueue数还有6个,这时候会开6个线程去执行,  目前达到10个最大线程数,此时队列里面还有10个。正好满足队列的大小

    static {
        System.out.println("核心线程数=" + CORE_POOL_SIZE);
        System.out.println("最大线程数=" + MAXIMUM_POOL_SIZE);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,  //核心线程数
                MAXIMUM_POOL_SIZE, //线程池中最大的线程数
                60,  //线程的存活时间,没事干的时候,空闲的时间
                TimeUnit.SECONDS, //线程存活时间的单位
                sPoolWorkQueue, //线程缓存队列
                new ThreadFactory() {  //线程创建工厂,如果线程池需要创建线程会调用newThread来创建
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setDaemon(false);
                        return thread;
                    }
                });
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_MY = threadPoolExecutor;
    }

    public static void main(String[] args) {
        // 创建线程池
        ThreadPoolExecutor service = new ThreadPoolExecutor(5, 200,
                0L, TimeUnit.MILLISECONDS,
                sPoolWorkQueue);
// 等待执行的runnable
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

// 启动的任务数量
        int counts = 1000;
        for (int i = 0; i < counts; i++) {
            service.execute(runnable);
        }

// 监控线程池执行情况的代码
        ThreadPoolExecutor tpe = ((ThreadPoolExecutor) service);
        while (true) {
            System.out.println();

            int queueSize = tpe.getQueue().size();
            System.out.println("当前排队线程数:" + queueSize);

            int activeCount = tpe.getActiveCount();
            System.out.println("当前活动线程数:" + activeCount);

            long completedTaskCount = tpe.getCompletedTaskCount();
            System.out.println("执行完成线程数:" + completedTaskCount);

            long taskCount = tpe.getTaskCount();
            System.out.println("总线程数:" + taskCount);

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程池的容量与我们启动的任务数量息息相关。
已知:
corePoolSize = 5
maximumPoolSize = 200
workQueue.size() = 1024
我们修改同时 execute 添加到线程池的 Runnable 数量 counts:

  • counts <= corePoolSize:所有的任务均为核心线程执行,没有任何 Runnable 被添加到 workQueue中
    当前排队线程数:0
    当前活动线程数:3
    执行完成线程数:0
    总线程数:3
  • corePoolSize < counts <= corePoolSize + workQueue.size():所有任务均为核心线程执行,当核心线程处于繁忙状态,则将任务添加到 workQueue 中等待
    当前排队线程数:15
    当前活动线程数:5
    执行完成线程数:0
    总线程数:20
  • corePoolSize + workQueue.size() < counts <= maximumPoolSize + workQueue.size():corePoolSize 个线程由核心线程执行,超出队列长度 workQueue.size() 的任务,将另启动非核心线程执行
    当前排队线程数:1024
    当前活动线程数:105
    执行完成线程数:0
    总线程数:1129
    counts > maximumPoolSize + workQueue.size():将会报异常java.util.concurrent.RejectedExecutionException
    java.util.concurrent.RejectedExecutionException: Task com.bwjava.util.ExecutorServiceUtilTest$$Lambda$1/314265080@725bef66 rejected from java.util.concurrent.ThreadPoolExecutor@2aaf7cc2[Running, pool size = 200, active threads = 200, queued tasks = 1024, completed tasks = 0]

你可能感兴趣的:(Java:线程池)