u13-多线程池

195 阅读9分钟

1. 线程池概念

概念: 一般情况下,一个请求需要至少开启一个线程来执行具体的请求内容,如果请求数量特别多,请求内容处理的时间非常短,则会造成频繁的创建和销毁线程,系统开销大。

  • 线程池优势:
    • 线程池可以维护多个线程的生命周期。
    • 线程池可以通过线程复用来提高线程的利用率。
    • 线程池可以提前创建好一批线程,在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟,这样,就可以立即为请求服务,使应用程序响应更快。
    • 线程池中可以适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。
  • 线程池缺点:
    • 线程池本身也是需要维护的,当线程数量特别少,或者执行时间特别长的时候,不建议使用。
    • 线程池容易遭受的并发风险,比同步错误,与池有关的死锁、资源不足和线程泄漏。

2. Callable

概念: Callable接口也是一个线程接口,内部配有一个带返回值的 call(),实现了Callable接口的线程可以被异步提交到线程池中,并由线程池来异步非阻塞地执行 call(),执行的结果会被线程池绑定到一个Future接口的实例中。

  • Callable构造:
    • Callable是一个函数式接口,可以使用匿名内部类或者lambda表示式来完成创建过程。
    • 在声明Callable实例的时候需要指定一个泛型,这个泛型就是 call() 方法的返回值类型。
  • Future常用方法:
    • V get(): 获取线程中 call() 方法的返回值,这个方法会阻塞,直到获取到结果。
    • boolean cancel(boolean mayInterruptIfRunning): 取消线程任务。
  • Callable VS Runnable:
    • 实现Runnable接口,重写线程体方法 run():返回值为void。
    • 实现Callable接口,重写线程体方法 call():返回值为接口泛型,会被放到一个Future中。

源码: /javase-advanced/

  • src: c.y.thread.pool.CallableTest
/**
 * @author yap
 */
public class CallableTest {

    @SneakyThrows
    @Test
    public void callable() {
        // can instead of lambda..
        Callable<Integer> callable = () -> {
            TimeUnit.SECONDS.sleep(2L);
            return 100;
        };

        ExecutorService executorService = Executors.newCachedThreadPool();

        // non-blocking
        Future<Integer> future = executorService.submit(callable);

        System.out.println("thread-main...");
        System.out.println("thread-main...");

        // blocking
        System.out.println(future.get());
        System.out.println("thread-main...");
        System.out.println("thread-main...");

        executorService.shutdown();
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

3. FutureTask

概念: FutureTask类既实现了Runnable接口,又实现了Future接口,所以它既能作为一个线程任务,又能直接将线程任务的结果进行绑定存储。

源码: /javase-advanced/

  • src: c.y.thread.pool.FutureTaskTest
/**
 * @author yap
 */
public class FutureTaskTest {
    @SneakyThrows
    @Test
    public void futureTask() {
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            TimeUnit.SECONDS.sleep(2L);
            return 100;
        });

        new Thread(futureTask).start();
        System.out.println("thread-main...");
        System.out.println("thread-main...");

        // blocking
        System.out.println(futureTask.get());
        System.out.println("thread-main...");
        System.out.println("thread-main...");
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

4. CompletableFuture

概念: CompletableFuture类可以对多个带有返回值的任务进行统一的管理。

  • static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
    • 异步执行一个带返回值的任务,并将任务结果进行存储。
  • static CompletableFuture<Void> runAsync(Runnable runnable)
    • 异步执行一个无回值的任务。
  • static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
    • 当所有指定的CompletableFuture全部都完成时,返回一个新的CompletableFuture。
  • static CompletableFuture<Void> anyOf(CompletableFuture<?>... cfs)
    • 当任意一个指定的CompletableFuture完成时,返回一个新的CompletableFuture。
  • T join()
    • 返回最终的结果。

源码: /javase-advanced/

  • src: c.y.thread.pool.CompletableFutureTest
/**
 * @author yap
 */
public class CompletableFutureTest {

    @SneakyThrows
    private String taskA() {
        TimeUnit.SECONDS.sleep(1L);
        System.out.println("taskA over...");
        return "taskA-over";
    }

    @SneakyThrows
    private String taskB() {
        TimeUnit.SECONDS.sleep(2L);
        System.out.println("taskB over...");
        return "taskB-over";

    }

    @SneakyThrows
    private String taskC() {
        TimeUnit.SECONDS.sleep(3L);
        System.out.println("taskC over...");
        return "taskC-over";
    }


    @SneakyThrows
    private void taskD() {
        TimeUnit.SECONDS.sleep(1L);
        System.out.println("taskD over...");
    }

    @SneakyThrows
    private void taskE() {
        TimeUnit.SECONDS.sleep(2L);
        System.out.println("taskE over...");

    }

    @SneakyThrows
    private void taskF() {
        TimeUnit.SECONDS.sleep(3L);
        System.out.println("taskF over...");
    }

    @SneakyThrows
    @Test
    public void performTask() {
        long start = System.currentTimeMillis();
        taskA();
        taskB();
        taskC();
        taskD();
        taskE();
        taskF();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    @SneakyThrows
    @Test
    public void performTaskByCompletableFuture() {
        long start = System.currentTimeMillis();
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(this::taskA);
        CompletableFuture<String> futureB = CompletableFuture.supplyAsync(this::taskB);
        CompletableFuture<String> futureC = CompletableFuture.supplyAsync(this::taskC);
        CompletableFuture<Void> futureD = CompletableFuture.runAsync(this::taskD);
        CompletableFuture<Void> futureE = CompletableFuture.runAsync(this::taskE);
        CompletableFuture<Void> futureF = CompletableFuture.runAsync(this::taskF);
        CompletableFuture<Void> future = CompletableFuture.allOf(futureA, futureB, futureC, futureD, futureE, futureF);
        future.join();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

5. ThreadPoolExecutor

概念: 线程池中维护了一个HashSet结构的线程集合和一个任务队列。

  • 构造:ThreadPoolExecutor(),共有七个参数:
    • corePoolSize: 核心线程数,线程池维护的最少线程数量,即使空闲也不会给归还给OS。
    • maximumPoolSize: 线程池维护的最多线程数量,即当核心线程不够了,最大能拓展到多少。
    • keepAliveTime: 非核心线程所允许的最大的空闲时间,某个线程的空闲时间如果超过此指定值,会被归还给OS。
    • unit: 最大的空闲时间单位。
    • workQueue: 线程池所使用的工作队列,BlockingQueue 类型。
    • threadFactory: 线程工厂,用于产生线程,可以自定义,默认使用DefaultThreadFactory。
    • handler: 线程池对拒绝任务采取的拒绝策略,RejectedExecutionHandler类型。
  • 方法:
    • void execute(Runnable command):将Runnable接口实例提交到线程池。
    • Future<T> submit(Callable<T> task):将Callable接口实例提交到线程池,并将返回值结果绑定给Future。
    • void shutdown():不再新增线程,发出停止信号,等所有线程执行完毕,关闭线程池,节省资源。
    • void shutdownNow():不再新增线程,立即关闭线程池,节省资源。

流程: 假设线程池设置核心线程数为2,最大线程数为4,工作队列为固定值2,最大存活10s:

  • 线程池最开始创建的时候,任务队列是空的。
  • 当调用了 execute() 添加任务时:
    • 当第1个任务来了,启动一个线程,为核心线程。
    • 当第2个任务来了,启动一个线程,为核心线程,此时核心线程数量已达最大。
    • 当第3个任务来了,任务进入队列阻塞。
    • 当第4个任务来了,任务进入队列阻塞,此时任务队列满了。
    • 当第5个任务来了,拓展一个新线程,处理这个任务。
    • 当第6个任务来了,拓展一个新线程,处理这个任务,此时到达线程数最大值。
    • 当第7个任务来了,执行拒绝策略。
  • 当一个线程完成了任务时,它会从等待队列中取下一个任务来执行。
  • 当一个线程无事可做超过10s,只要不是核心线程,就停用掉。

源码: /javase-advanced/

  • src: c.y.thread.pool.ThreadPoolExecutorTest
/**
 * @author yap
 */
public class ThreadPoolExecutorTest {

    @SneakyThrows
    @Test
    public void threadPoolExecutor() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, 4, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        threadPool.execute(() -> {
            System.out.println("runnable...");
        });

        TimeUnit.SECONDS.sleep(2L);

        Future<Integer> future = threadPool.submit(() -> {
            System.out.println("callable...");
            return 100;
        });
        System.out.println(future.get());
    }


    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

6. 线程池拒绝策略

概念: 线程池拒绝策略指的是当线程数已达最大时,对新申请加入的线程的拒绝方式,内置有四种:

  • AbortPolicy:直接抛出异常,阻止系统正常工作。
  • DiscardPolicy:直接啥事都不干,直接把任务丢弃。
  • DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个),让新来的任务入队。
  • CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务,即哪个线程调用了线程池的execute()方法,哪个线程执行新来的任务。

源码: /javase-advanced/

  • src: c.y.thread.pool.RejectionStrategyTest
/**
 * @author yap
 */
public class RejectionStrategyTest {

    private static class MyTask implements Runnable {
        private int i;

        private MyTask(int i) {
            this.i = i;
        }

        @SneakyThrows
        @Override
        public void run() {
            System.out.println(Thread.currentThread() + ": " + i);
        }

        @Override
        public String toString() {
            return "MyTask{" + "i=" + i + "}";
        }
    }

    private void policy(RejectedExecutionHandler policy) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, 4, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),
                policy);

        for (int i = 0, j = 8; i < j; i++) {
            threadPool.execute(new MyTask(i));
        }

        System.out.println("queue:" + threadPool.getQueue());
        threadPool.execute(new MyTask(100));
        System.out.println("queue:" + threadPool.getQueue());
        threadPool.shutdown();
    }

    @Test
    public void abortPolicy() {
        policy(new ThreadPoolExecutor.AbortPolicy());
    }

    @Test
    public void discardPolicy() {
        policy(new ThreadPoolExecutor.DiscardPolicy());
    }

    @Test
    public void discardOldestPolicy() {
        policy(new ThreadPoolExecutor.DiscardOldestPolicy());
    }

    @Test
    public void callerRunsPolicy() {
        policy(new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @SneakyThrows
    @After
    public void after(){
        System.out.println(System.in.read());
    }

}

7. Executor框架

概念: 尽量避免使用Executor框架创建线程池,因为创建线程时用的内存并不是jvm堆内存,而是系统直接内存,而Executor大部分框架允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

7.1 CachedThreadPool

概念: CachedThreadPool是一个可缓存的线程池。

  • 核心线程数为0,即所有线程都可以被回收。
  • 最大线程数为Integer.MAX_VALUE,几乎接近于无限制,会导致OOM。
  • 线程最大空闲时间为60s。
  • 工作队列为 SynchronousQueue 同步队列,多用于手递手传递数据,几乎无队列等待现象,适用于处理大量耗时短的任务。
  • 构造:Executors.newCachedThreadPool():

源码: /javase-advanced/

  • src: c.y.thread.pool.CachedThreadPoolTest
/**
 * @author yap
 */
public class CachedThreadPoolTest {

    @Test
    public void cachedThreadPool() throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0, j = 10; i < j; i++) {
            TimeUnit.SECONDS.sleep(1);
            executorService.execute(() -> {
                System.out.println("hello!");
            });
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

7.2 FixedThreadPool

概念: FixedThreadPool是一个线程数固定的,无序的线程池。

  • 核心线程数需要在构造的时候指定,一旦指定不可更改。
  • 最大线程数和核心线程数一致,即所有线程都是核心线程。
  • 线程最大空闲时间为0s,空闲时间对于核心线程来说是被忽略的。
  • 工作队列为 LinkedBlockingQueue 无界阻塞队列,容易OOM。
  • 构造:Executors.newFixedThreadPool(2):

源码: /javase-advanced/

  • src: c.y.thread.pool.FixedThreadPoolTest
/**
 * @author yap
 */
public class FixedThreadPoolTest {

    @Test
    public void cachedThreadPool() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0, j = 10; i < j; i++) {
            TimeUnit.SECONDS.sleep(1);
            executorService.execute(() -> {
                System.out.println("hello!");
            });
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

7.3 ScheduledThreadPool

概念: ScheduledThreadPool是一个周期线程池,可以延时或者周期地执行任务。

  • 构造:Executors.newScheduledThreadPool(1):
  • 方法:
    • schedule():延迟多久后执行任务。
      • param1: 执行任务的Callable或Runnable接口实例。
      • param2: 延时执行任务的延迟时间。
      • param3: 时间单位。
    • ScheduledFuture<?> scheduleAtFixedRate()
      • param1: 执行任务的Callable或Runnable接口实例。
      • param2: 第一次执行任务的延迟时间。
      • param3: 连续执行周期任务的时间间隔,从上一个任务开始执行时始计算。
      • param4: 时间单位。
    • ScheduledFuture<?> scheduleWithFixedDelay()
      • param1: 执行任务的Callable或Runnable接口实例。
      • param2: 第一次执行任务的延迟时间。
      • param3: 连续执行周期任务的时间间隔,从上一个任务执行结束时计算。
      • param4: 时间单位。

源码: /javase-advanced/

  • src: c.y.thread.pool.ScheduledThreadPoolTest
/**
 * @author yap
 */
public class ScheduledThreadPoolTest {

    @Test
    public void schedule() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.schedule(() -> {
            System.out.println("delay 2s and print");
        }, 2, TimeUnit.SECONDS);
    }

    @Test
    public void scheduleAtFixedRate() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleAtFixedRate(() -> {
            System.out.println("delay 2s and every 1s");
        }, 2,1, TimeUnit.SECONDS);
    }

    @Test
    public void scheduleWithFixedDelay() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleWithFixedDelay(() -> {
            System.out.println("delay 2s and every 1s");
        }, 2,1, TimeUnit.SECONDS);
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

7.4 SingleThreadPool

概念: SingleThreadExecutor是只拥有唯一线程来执行任务的线程池。

  • 核心线程数为1,最大线程数为1,即线程池中只有一个核心线程。
  • 线程最大空闲时间为0s,空闲时间对于核心线程来说是被忽略的。
  • 工作队列为 LinkedBlockingQueue 无界阻塞队列,容易OOM。
  • 构造:Executors.newSingleThreadExecutor():
  • SingleThreadExecutor一般用户保证所有任务按照指定的顺序执行。
  • 这个唯一的线程不能被更改。
  • 构造:Executors.newSingleThreadExecutor():

源码: /javase-advanced/

  • src: c.y.thread.pool.SingleThreadPoolTest
/**
 * @author yap
 */
public class SingleThreadPoolTest {

    @Test
    public void cachedThreadPool() throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0, j = 10; i < j; i++) {
            TimeUnit.SECONDS.sleep(1);
            executorService.execute(() -> {
                System.out.println("hello!");
            });
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

8. 自定义线程池

概念: Executors中提供的线程池框架或多或少都有一些局限性和不足,我们工作中通常自定义线程池的参数来灵活控制线程池的特性:

  • 自定义线程池工厂:
    • 实现ThreadFactory接口。
    • 重写 Thread newThread(Runnable r)
    • 创建可以自定义命名的线程。
  • 自定义拒绝策略:
    • 实现RejectedExecutionHandler接口。
    • 重写 void rejectedExecution(Runnable r, ThreadPoolExecutor e)
    • 将未能处理的任务保存到redis或者数据库中,在其他时间进行处理。

源码: /javase-advanced/

  • src: c.y.thread.pool.CustomThreadPoolTest
/**
 * @author yap
 */
public class CustomThreadPoolTest {

    private static class CustomThreadFactory implements ThreadFactory {

        private final AtomicInteger threadId = new AtomicInteger(0);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "myThread-" + threadId.getAndIncrement());
        }
    }

    private static class CustomRejectedPolicy implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            System.out.println(((CustomTask) r).getTaskName() + " is rejected!");
        }
    }

    @AllArgsConstructor
    @Data
    private static class CustomTask implements Runnable {
        private String taskName;

        @SneakyThrows
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ": " + taskName + " is running!");
            TimeUnit.SECONDS.sleep(3L);
        }
    }

    @Test
    @SneakyThrows
    public void customThreadPool() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, 4, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new CustomThreadFactory(),
                new CustomRejectedPolicy());

        for (int i = 0, j = 8; i < j; i++) {
            threadPool.execute(new CustomTask("myTask-" + i));
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}