Java中创建线程池的正确方法

Executors的创建线程池的方法,创建出来的线程池都实现了ExecutorService接口。常用方法有以下几个:

a、newFiexedThreadPool(int Threads):创建固定数目线程的线程池。

b、newCachedThreadPool():创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

c、newSingleThreadExecutor()创建一个单线程化的Executor。

d、newScheduledThreadPool(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

看起来功能还是比较强大的,又用到了工厂模式、又有比较强的扩展性,重要的是用起来还比较方便,如:

ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
即可创建一个固定大小的线程池。

但是为什么我说不建议大家使用这个类来创建线程池呢?

Executors存在什么问题
在阿里巴巴Java开发手册中提到,使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出),但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?

我们先来一个简单的例子,模拟一下使用Executors导致OOM的情况

public class ExecutorsDemo {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}


通过指定JVM参数:-Xmx8m -Xms8m 运行以上代码,会抛出OOM:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

以上代码指出,ExecutorsDemo.java的第16行,就是代码中的executor.execute(new SubThread());。

Executors为什么存在缺陷
通过上面的例子,我们知道了Executors创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们需要深入Executors的源码来分析一下。

其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是LinkedBlockingQueue.offer方法。

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

翻看代码的话,也可以发现,其实底层确实是通过LinkedBlockingQueue实现的:

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


如果读者对Java中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。

Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。

LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。

这里的问题就出在:**不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。**也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。

而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。

上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。

创建线程池的正确姿势
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。

作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。

public class ExecutorsDemo {

    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }
}

转载文章,备看备学!

原文:https://blog.youkuaiyun.com/hollis_chuang/article/details/83743723

Java创建线程池并调用静态方法是常见的并发编程需求。可以通过 `java.util.concurrent.Executors` 类来创建线程池,并使用 `ExecutorService` 来提交任务,包括调用静态方法的任务。 ### 创建线程池 Java 提供了多种类型的线程池,比如固定大小的线程池、缓存线程池等。以下是一个使用 `Executors.newFixedThreadPool()` 创建线程池的示例: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { // 创建一个固定大小为3的线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); // 提交多个任务到线程池中 for (int i = 0; i < 5; i++) { executorService.submit(new Task()); } // 关闭线程池,等待所有任务执行完成 executorService.shutdown(); } } ``` ### 调用静态方法 为了在线程池中调用静态方法,可以将静态方法封装在 `Runnable` 或 `Callable` 中,然后通过 `ExecutorService.submit()` 提交任务。例如,定义一个包含静态方法的类: ```java public class StaticMethodClass { // 静态方法 public static void performStaticTask() { System.out.println("Executing static method in thread: " + Thread.currentThread().getName()); } } ``` 然后修改 `Task` 类以调用该静态方法: ```java public class Task implements Runnable { @Override public void run() { // 调用静态方法 StaticMethodClass.performStaticTask(); } } ``` 这样,线程池中的每个线程都会执行 `StaticMethodClass.performStaticTask()` 方法。 ### 同步与线程安全 如果多个线程同时调用同一个类的静态同步方法(如 `synchronized static` 方法),那么这些方法会共享同一个类锁(即 `StaticMethodClass.class` 的锁)。在这种情况下,一次只能有一个线程执行这些静态同步方法[^2]。如果你希望多个线程能够并发执行不同的静态方法,则需要确保这些方法不是同步的,或者使用其他机制(如 `ReentrantLock`)进行更细粒度的控制。 ### 异常处理与中断 当线程池中的任务调用了阻塞方法时,可能会导致线程被阻塞。此时,可以通过调用 `Thread.interrupt()` 方法来中断线程,并捕获 `InterruptedException` 进行清理工作[^3]。例如: ```java public class Task implements Runnable { @Override public void run() { try { // 模拟一个可能阻塞的操作 Thread.sleep(1000); StaticMethodClass.performStaticTask(); } catch (InterruptedException e) { System.out.println("Task was interrupted: " + Thread.currentThread().getName()); Thread.currentThread().interrupt(); // 重新设置中断标志 } } } ``` ### 类初始化与静态代码块 当线程首次访问某个类时,JVM 会加载并初始化该类。在这个过程中,静态变量和静态代码块会按照源码顺序执行[^4]。因此,在静态方法中使用的静态变量应当确保已经正确初始化。 ### 示例总结 完整的代码如下所示: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executorService.submit(new Task()); } executorService.shutdown(); } } class StaticMethodClass { static { System.out.println("Static block executed."); } public static void performStaticTask() { System.out.println("Executing static method in thread: " + Thread.currentThread().getName()); } } class Task implements Runnable { @Override public void run() { try { Thread.sleep(1000); StaticMethodClass.performStaticTask(); } catch (InterruptedException e) { System.out.println("Task was interrupted: " + Thread.currentThread().getName()); Thread.currentThread().interrupt(); } } } ``` 上述代码展示了如何创建线程池、调用静态方法、处理中断以及类初始化过程。 ---
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值