Java线程池快速上手

104 阅读3分钟

线程池

线程池分类

1. Executors.newSingleThreadExecutor(); // 创建只有一个线程的线程池
2. Executors.newCachedThreadPool(); // 创建可以存放多个线程的线程池
3. Executors.newFixedThreadPool(线程数); // 创建拥有固定线程数的线程池
4. Executors.newSingleThreadScheduledExecutor(); // 一个可以用于“线程调度”的单个线程的线程池(即可以指定时间来执行)
5. Executors.newScheduledThreadPool(); // 一个可以用于“线程调度”的多个线程的线程池(即可以指定时间来执行)

前三种创建ExecutorService类型的线程池,继承于Executor

后两种创建ScheduledExecutorService类型的线程池,继承于ExecutorService

线程池使用步骤

① 创建线程池

② 将任务通过submit()scheduled()提交到线程池中的线程

③ 线程通过run()方法或call()方法执行任务,并将执行结果返回到Future对象

④ Future对象通过get()方法以闭锁的方式拿到真实处理后的返回值

自定义线程池

基本介绍

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              [ThreadFactory threadFactory],
                              RejectedExecutionHandler handler)

参数说明:

corePoolSize 核心线程数量

  • 即使没有任务执行,核心线程也会一直存活
  • 线程数小于核心线程时,即使有空闲线程,线程池也会创建新线程执行任务
  • 设置allowCoreThreadTimeout= true时,核心线程会超时关闭

maximumPoolSize 最大线程数

  • 当所有核心线程都在执行任务,且任务队列已满时,线程池会创建新线程执行任务
  • 当线程数=maxPoolSize,且任务队列已满,此时添加任务时会触发RejectedExecutionHandler进行处理

keepAliveTimeTimeUnit 线程空闲时间 及 单位

  • 指的是线程池中非核心线程的最大空闲时长
  • 如果非核心线程空闲时间达到keepAliveTime时,线程会销毁
  • 如果设置allowCoreThreadTimeout= true时,核心线程执行完任务也会销毁直到数量 = 0

workQueue 任务队列

  • ArrayBlockingQueue 有界队列,需要指定队列大小
  • LinkedBlockingQueue 若指定大小则和ArrayBlockingQueue类似,若不指定大小则默认能存储Integer.MAX_VALUE个任务,相当于无界队列,此时maximumPoolSize值其实是无意义的
  • SynchronousQueue 同步阻塞队列,当有任务添加进来后,必须有线程从队列中取出,当前线程才会被释放,newCachedThreadPool就使用这种队列

handler 拒绝策略

饱和定义为:任务数 > maximumPoolSize + workQueue.size()

  • AbortPolicy 默认的拒绝策略,如果饱和直接抛出异常
  • CallerRunsPolicy 直接调用execute去执行
  • DiscardPolicy 饱和后直接抛弃任务,不抛出异常
  • DiscardOldestPolicy 抛弃队列中最先加入的任务,然后再添加新任务

代码实验

  1. 自定义线程
public class MyThread implements Runnable{
    private String threadName;
​
    public MyThread(String threadName){
        this.threadName = threadName;
    }
​
    @Override
    public void run() {
        try {
            System.out.println("threadName: " + this.threadName);
            System.out.println(threadName + "执行了1s");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
​
    public String getThreadName() {
        return threadName;
    }
​
    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }
}
  1. 测试类
public class TestMyThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.AbortPolicy()
                );
​
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");
        MyThread t3 = new MyThread("t3");
        MyThread t4 = new MyThread("t4");
        MyThread t5 = new MyThread("t5");
        MyThread t6 = new MyThread("t6");
​
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
    }
}

分析

任务到达时,将其先尝试分配给核心线程,如果核心线程满了,则放到工作队列中,工作队列也满了才将任务交给非核心进程执行,此实验核心进程数为1,非核心进程数为1,工作队列进程容量为3,任务为6,刚好可以测出所有的情况。

threadName: t1
t1执行了1s
threadName: t5
t5执行了1s
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task diyPool.MyThread@677327b6 rejected from java.util.concurrent.ThreadPoolExecutor@14ae5a5[Running, pool size = 2, active threads = 2, queued tasks = 3, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    at diyPool.TestMyThreadPool.main(TestMyThreadPool.java:27)
threadName: t2
t2执行了1s
threadName: t3
t3执行了1s
threadName: t4
t4执行了1s

扩展

使用自定义策略

  1. 定义
public class MyRejectPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("被拒绝的线程名:" + ((MyThread)r).getThreadName());
    }
}
  1. 使用(在对应位置更换拒绝策略)
//                new ThreadPoolExecutor.AbortPolicy()
                new MyRejectPolicy()