线程池入门学习

一 线程池基本介绍

1.1 什么是线程池

线程池是并发编程用到的技术,用于管理和复用线程的一种技术;
线程池本质是一种池化技术,池化技术是一种资源复用的思想;

1.2 为什么要使用线程池

降低资源消耗,线程池复用创建完成的线程,可以避免线程的频繁创建和销毁带来的性能开销,因为创建线程涉及到CPU的上下文切换和内存分配工作;
提高响应速度,当任务到达时可以直接使用线程池里面已经创建好的线程执行线程任务,提高了系统的响应速度;
控制最大并发线程数,线程池可以帮助控制同时执行的线程数量,避免系统因创建大量线程而导致的负载过高;
提高线程的可管理性,线程池提供了一个统一的框架来管理和监控线程,包括线程的创建、销毁和调度,这有助于提高系统稳定性,避免因无限制地创建和销毁线程而导致的资源消耗和系统不稳定;
提高可伸缩性,线程池可以根据需要动态调整线程数量,避免过多或过少的线程造成的资源浪费或性能瓶颈;

1.3 线程池的五种状态

RUNNING,线程池正在运行,可以接受并处理任务;
SHUTDOWN,线程池关闭,不再接受新任务,但会处理阻塞队列中剩余的任务;
STOP,线程池关闭,不接受新任务,不处理阻塞队列中的任务,并会中断正在执行的任务;
TIDYING,所有任务已终止,工作计数为0,线程池处于此状态时会执行terminated()钩子函数;
TERMINATED,线程池执行完terminated()函数后进入此状态,表示线程池彻底终止;

1.4 线程池特性

当线程池分配到任务,线程池没有空闲的线程,线程池通过ThreadFactory线程工厂创建一个线程执行任务,当任务执行完毕后,线程将返回到线程池,被其他任务复用;
当线程池分配到任务,线程池有空闲的线程,将任务分配给空闲线程执行,当任务执行完毕后,线程将返回到线程池,被其他任务复用;
线程池里面的核心线程一直处于运行状态,会接收新任务和获取阻塞队列中待执行的任务处理,一旦没有新任务进来和阻塞队列清空,那么核心线程就会被阻塞,直到有新的任务进来,核心线程默认是长期存在的工作线程;
通过配置线程池工作线程的大小和阻塞队列的容量,充分利用系统资源;

线程池创建有两种方式,使用Executors线程池工具类创建线程池,和使用ThreadPoolExecutor类自定义线程池;

二 Executors工具类

java.util.concurrent.Executors线程池工具类,类中都是一些静态方法用于创建线程池对象,方法内部调用的还是ThreadPoolExecutor类,Executors线程池工具类常用方法:
public static ExecutorService newSingleThreadExecutor():创建一个单线程化的线程池,即只有一个工作线程来执行任务,保证所有任务按照FIFO顺序执行;
public static ExecutorService newFixedThreadPool(int nThreads):创建一个指定线程数量的线程池,超过指定线程数量的线程任务放入到队列里面等待;
public static ExecutorService newCachedThreadPool():创建一个缓存型的线程池,如果线程池中有活跃的线程,就直接执行线程任务,否则就创建一个新的线程加入线程池中,线程空闲60秒会被销毁;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个支持定时执行任务和周期性执行任务的线程池,即是可以延时执行任务,也可以周期性执行任务;
public static ExecutorService newWorkStealingPool(int parallelism):创建ForkJoinPool线程池,用于并行计算的线程池;

2.1 单线程化的线程池

单线程化的线程池适用于需要顺序地执行各个任务,这个线程能够保证所有线程任务都执行完成;
如果在执行线程任务时抛出异常,那么当前线程会终止掉,并且再创建一个新线程继续执行后面的线程任务;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Runnable r1 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r2 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb...");
            System.out.println(1/0);
            System.out.println("任务抛出异常结束, 此步骤不会执行到...");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r3 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出ccc...");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    try {
        executorService.execute(r1);
        executorService.execute(r1);
        executorService.execute(r2);
        executorService.execute(r3);
    } finally {
        executorService.shutdown();
    }
    // pool-1-thread-1线程执行任务输出aaa...
    // pool-1-thread-1线程执行任务输出aaa...
    // pool-1-thread-1线程执行任务输出bbb...
    // pool-1-thread-2线程执行任务输出ccc...
    // Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    //     at com.mango.executors.ThreadOne.lambda$main$1(ThreadOne.java:24)
    //     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    //     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    //     at java.lang.Thread.run(Thread.java:748)
}

当线程pool-1-thread-1执行任务r2时抛出异常,那么线程pool-1-thread-1终止,并创建线程pool-1-thread-2继续执行后面的线程任务r3

2.2 指定线程数量的线程池

指定线程数量的线程池,当线程处于空闲状态时不会被回收,除非线程池被关闭,适用于执行长期的任务;
当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来;
如果在执行线程任务时抛出异常,而且还没有空闲线程,那么当前线程会终止掉,并且再创建一个新线程继续执行后面的线程任务;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    Random random = new Random();
    Runnable r1 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa...");
            Thread.sleep(random.nextInt(10) * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r2 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb...");
            System.out.println(1/0);
            System.out.println("任务抛出异常结束, 此步骤不会执行到...");
            Thread.sleep(random.nextInt(10) * 100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    try {
        for (int i = 0; i < 10; i++) {
            executorService.execute(r1);
        }
        executorService.execute(r2);
        for (int i = 10; i < 25; i++) {
            executorService.execute(r1);
        }
    } finally {
        executorService.shutdown();
    }

}

2.3 缓存型线程池

缓存型线程池,当有线程任务时,如果有空闲线程则执行线程任务,如果没有空闲线程则创建新的线程执行任务;
缓存型线程池中的线程空闲时间超过60秒就会被销毁;
缓存型线程池经常用来执行一些业务处理时间很短的任务;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Random random = new Random();
    Runnable r1 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa...");
            Thread.sleep(random.nextInt(10) * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r2 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb...");
            System.out.println(1/0);
            System.out.println("任务抛出异常结束, 此步骤不会执行到...");
            Thread.sleep(random.nextInt(10) * 100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    try {
        for (int i = 0; i < 5; i++) {
            executorService.execute(r1);
        }
        TimeUnit.MINUTES.sleep(2);
        executorService.execute(r2);
        for (int i = 5; i < 10; i++) {
            executorService.execute(r1);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        executorService.shutdown();
    }
    // pool-1-thread-3线程执行任务输出aaa...
    // pool-1-thread-1线程执行任务输出aaa...
    // pool-1-thread-5线程执行任务输出aaa...
    // pool-1-thread-4线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
    // pool-1-thread-6线程执行任务输出bbb...
    // pool-1-thread-7线程执行任务输出aaa...
    // pool-1-thread-8线程执行任务输出aaa...
    // pool-1-thread-9线程执行任务输出aaa...
    // pool-1-thread-10线程执行任务输出aaa...
    // pool-1-thread-11线程执行任务输出aaa...
    // Exception in thread "pool-1-thread-6" java.lang.ArithmeticException: / by zero
    //     at com.mango.executors.ThreadOne.lambda$main$1(ThreadOne.java:27)
    //     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    //     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    //     at java.lang.Thread.run(Thread.java:748)
}

前5个线程执行完任务后,空闲时间超过60秒,然后前5个线程就销毁了;
再执行任务r2时,需要再创建新的线程执行;
如果在执行线程任务时抛出异常,而且还没有空闲线程,那么当前线程会终止掉,并且再创建一个新线程继续执行后面的线程任务;

2.4 定时执行的线程池

定时执行的线程池支持延时执行任务,也支持周期性执行任务,也是一个固定指定线程数量的线程池;
ScheduledExecutorService常用到的三个方法:
public ScheduledFuture<?> schedule(Runnable command,  long delay, TimeUnit unit)
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

2.4.1 schedule()方法

public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    try {
        for (int i = 0; i < 10; i++) {
            executorService.schedule(() -> {
                System.out.println(Thread.currentThread().getName()
                        + "线程执行任务输出aaa...");
            }, 3, TimeUnit.SECONDS);
        }
    } finally {
        executorService.shutdown();
    }
    // pool-1-thread-1线程执行任务输出aaa...
    // pool-1-thread-3线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
    // pool-1-thread-3线程执行任务输出aaa...
    // pool-1-thread-1线程执行任务输出aaa...
    // pool-1-thread-3线程执行任务输出aaa...
    // pool-1-thread-2线程执行任务输出aaa...
}

schedule(Runnable command, long delay, TimeUnit unit),延迟指定的delay时间后,开始执行线程任务

2.4.2 scheduleAtFixedRate()

通过如下两段代码比较:

public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    executorService.scheduleAtFixedRate(() -> {
        try {
            DateTimeFormatter formatter = 
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa, 当前时间是"
                    + LocalDateTime.now().format(formatter));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb, 当前时间是"
                    + LocalDateTime.now().format(formatter));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 3, TimeUnit.SECONDS);
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:18:07
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:18:08
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:18:10
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:18:11
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:18:13
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:18:14
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:18:16
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:18:17
}
public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    executorService.scheduleAtFixedRate(() -> {
        try {
            DateTimeFormatter formatter = 
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa, 当前时间是"
                    + LocalDateTime.now().format(formatter));
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb, 当前时间是"
                    + LocalDateTime.now().format(formatter));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 3, TimeUnit.SECONDS);
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:20:38
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:20:43
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:20:43
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:20:48
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:20:48
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:20:53
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:20:53
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:20:58
}

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),首先依据initialDelay时间初始化执行线程任务,然后依据period固定时间间隔执行线程任务;
如果线程任务的执行时间小于period固定时间间隔,那么线程任务依次开始执行时间就是上次任务完成时间加上period固定时间间隔;
如果线程任务的执行时间大于period固定时间间隔,那么线程任务依次开始执行时间就是上次任务完成时间加上任务的执行时间;

2.4.3 scheduleWithFixedDelay()

通过如下两段代码比较:

public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    executorService.scheduleWithFixedDelay(() -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa, 当前时间是"
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb, 当前时间是"
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 3, TimeUnit.SECONDS);
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:29:30
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:29:31
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:29:34
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:29:35
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:29:38
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:29:39
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:29:42
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:29:43
}
public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
    executorService.scheduleWithFixedDelay(() -> {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa, 当前时间是"
                    + LocalDateTime.now().format(formatter));
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb, 当前时间是"
                    + LocalDateTime.now().format(formatter));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 3, TimeUnit.SECONDS);
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:35:05
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:35:10
    // pool-1-thread-1线程执行任务输出aaa, 当前时间是2024-04-06 18:35:13
    // pool-1-thread-1线程执行任务输出bbb, 当前时间是2024-04-06 18:35:18
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:35:21
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:35:26
    // pool-1-thread-2线程执行任务输出aaa, 当前时间是2024-04-06 18:35:29
    // pool-1-thread-2线程执行任务输出bbb, 当前时间是2024-04-06 18:35:34 
}

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit),首先依据initialDelay时间初始化执行线程任务,然后线程任务下次开始时间是线程任务上次完成时间加上period固定时间间隔,依次执行线程任务;

2.5 ForkJoinPool线程池

ThreadPoolExecutor类型的线程池有多个工作线程,但是只有一个阻塞队列,而ForkJoinPool线程池的每一个线程都有一个自己的阻塞队列;
ForkJoinPool线程池是用于并行计算的线程池,使用工作窃取算法优化任务的分配和执行,工作窃取算法是指空闲线程从其他线程的队列里获取任务执行;
工作线程从本地队列使用LIFO(后进先出)方式获取任务,使用FIFO(先进先出)方式获取别的队列任务,优化线程的利用率;
public static ExecutorService newWorkStealingPool(int parallelism):创建指定线程数量的线程池;
public static ExecutorService newWorkStealingPool():创建当前系统CPU核数的线程池;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newWorkStealingPool(3);
    CountDownLatch countDownLatch = new CountDownLatch(30);
    Random random = new Random();
    Runnable r1 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出aaa...");
            Thread.sleep(random.nextInt(10) * 10);
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r2 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出bbb...");
            Thread.sleep(random.nextInt(10) * 500);
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    Runnable r3 = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程执行任务输出ccc...");
            Thread.sleep(random.nextInt(10) * 5000);
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    try {
        for (int i = 0; i < 10; i++) {
            executorService.execute(r1);
            executorService.execute(r2);
            executorService.execute(r3);
        }
    } finally {
        executorService.shutdown();
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // ForkJoinPool-1-worker-2线程执行任务输出bbb...
    // ForkJoinPool-1-worker-3线程执行任务输出ccc...
    // ForkJoinPool-1-worker-1线程执行任务输出aaa...
    // ForkJoinPool-1-worker-1线程执行任务输出aaa...
    // ForkJoinPool-1-worker-1线程执行任务输出bbb...
    // ForkJoinPool-1-worker-2线程执行任务输出ccc...
    // ForkJoinPool-1-worker-1线程执行任务输出aaa...
    // ForkJoinPool-1-worker-1线程执行任务输出bbb...
    // ForkJoinPool-1-worker-1线程执行任务输出ccc...
    // ForkJoinPool-1-worker-3线程执行任务输出aaa...
    // ForkJoinPool-1-worker-3线程执行任务输出bbb...
    // ForkJoinPool-1-worker-3线程执行任务输出ccc...
    // ForkJoinPool-1-worker-1线程执行任务输出aaa...
    // ForkJoinPool-1-worker-1线程执行任务输出bbb...
    // ForkJoinPool-1-worker-1线程执行任务输出ccc...
    // ForkJoinPool-1-worker-2线程执行任务输出aaa...
    // ForkJoinPool-1-worker-2线程执行任务输出bbb...
    // ForkJoinPool-1-worker-2线程执行任务输出ccc...
    // ForkJoinPool-1-worker-3线程执行任务输出aaa...
    // ForkJoinPool-1-worker-3线程执行任务输出bbb...
    // ForkJoinPool-1-worker-3线程执行任务输出ccc...
    // ForkJoinPool-1-worker-3线程执行任务输出aaa...
    // ForkJoinPool-1-worker-3线程执行任务输出bbb...
    // ForkJoinPool-1-worker-3线程执行任务输出ccc...
    // ForkJoinPool-1-worker-1线程执行任务输出aaa...
    // ForkJoinPool-1-worker-1线程执行任务输出bbb...
    // ForkJoinPool-1-worker-1线程执行任务输出ccc...
    // ForkJoinPool-1-worker-3线程执行任务输出aaa...
    // ForkJoinPool-1-worker-3线程执行任务输出bbb...
    // ForkJoinPool-1-worker-2线程执行任务输出ccc...
}

三 自定义线程池

线程池类关系图:

Executors线程池工具类,类中都是一些静态方法用于创建线程池对象,但是方法内部调用的还是ThreadPoolExecutor类的方法,最好不要使用Executors工具类去创建线程,可能引起资源OOM;
Executor接口是线程池顶级接口,ExecutorService接口是线程池接口,使用ThreadPoolExecutor类的自定义线程池,合理配置线程池的参数,使得创建的线程池更加灵活并且安全;

3.1 ThreadPoolExecutor类

使用ThreadPoolExecutor类的构造方法自定义线程池:

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

构造方法参数介绍:
        corePoolSize:核心线程数量;
        maximumPoolSize:最大线程池数量;
        keepAliveTime:非核心线程空闲时间;
        unit:非核心线程空闲时间单位;
        workQueue:任务阻塞队列;
        threadFactory:创建线程对象的方式;
        handler:任务过多时的解决方案;
BlockingQueue参数:
任务阻塞队列分为无界队列LinkedBlockingQueue和有界队列ArrayBlockingQueue;
LinkedBlockingQueue队列基于链表实现的阻塞队列,链表长度最大值是Integer.MAX_VALUE,当线程任务执行时间较长时,有可能会导致大量任务堆积在阻塞队列,出现OOM;
ArrayBlockingQueue队列基于数组实现的阻塞队列,有界队列可以降低cpu、内存资源的使用;
ThreadFactory参数:
ThreadFactory线程工程用来创建线程对象,可以自定义线程对象信息

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new ThreadFactory() {
                AtomicInteger atomicInteger = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "self-"
                            + atomicInteger.getAndIncrement());
                }
    });
    try {
        for (int i = 0; i < 3; i++) {
            executor.execute(() -> {
                System.out.println("thread name is: "
                        + Thread.currentThread().getName());
            });
        }
        // thread name is: self-1
        // thread name is: self-2
        // thread name is: self-0
    } finally {
        executor.shutdown();
    }
}

RejectedExecutionHandler是自定义线程池的拒绝策略:

ThreadPoolExecutor.AbortPolicy:丢弃当前最新任务,并会抛出RejectedExecutionException异常,默认策略;
ThreadPoolExecutor.DiscardPolicy:丢弃当前最新任务,并且不抛出异常,适用于线程任务不重要的情况;
ThreadPoolExecutor.DiscardOldestPolicy:丢弃任务队列中等待最久的任务,然后把当前任务加入到队列中;
ThreadPoolExecutor.CallerRunsPolicy:使用调用当前任务的线程执行任务的run()方法,不使用线程池的线程执行任务;

3.2 线程池的基本流程

在应用程序启动时创建线程池,此时线程池里面没有线程对象;
当线程池分配到任务,且没有空闲线程时,和当前的工作线程数小于核心线程数,创建核心线程执行任务,直到核心线程创建完全;
当线程池分配到任务,且没有空闲线程时,和当前的工作线程数等于核心线程数,将任务放入阻塞队列,直到阻塞队列被占满;
当线程池分配到任务,且没有空闲线程时,和当前的工作线程数等于核心线程数,和阻塞队列放满了任务,创建临时线程执行任务,直到临时线程创建完全;
当线程池分配到任务,且没有空闲线程时,和当前的工作线程数等于最大线程数,和阻塞队列放满了任务,线程池满负荷工作时,触发任务的拒绝策略;
线程池的所有任务全部完成后, 线程池会收缩到core Pool Size的大小,最后需要关闭线程池;

3.3 ThreadPoolExecutor应用

public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new ThreadFactory() {
                AtomicInteger atomicInteger = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "self-"
                            + atomicInteger.getAndIncrement());
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    Runnable r = () -> {
        try {
            System.out.println(Thread.currentThread().getName()
                    + "线程开始执行任务...");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    try {
        for (int i = 0; i < 20; i++) {
            executor.execute(r);
        }
    } finally {
        executor.shutdown();
    }
    // self-1线程开始执行任务...
    // self-2线程开始执行任务...
    // self-3线程开始执行任务...
    // self-0线程开始执行任务...
    // self-6线程开始执行任务...
    // self-7线程开始执行任务...
    // self-4线程开始执行任务...
    // self-5线程开始执行任务...
    // self-9线程开始执行任务...
    // self-8线程开始执行任务...
    // self-9线程开始执行任务...
    // self-3线程开始执行任务...
    // self-6线程开始执行任务...
    // self-0线程开始执行任务...
    // self-4线程开始执行任务...
    // self-5线程开始执行任务...
    // self-2线程开始执行任务...
    // self-8线程开始执行任务...
    // self-7线程开始执行任务...
    // self-1线程开始执行任务...
}

tips:

ThreadPoolExecutor常用方法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值