iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >java8异步调用如何使用才是最好的方式
  • 299
分享到

java8异步调用如何使用才是最好的方式

2024-04-02 19:04:59 299人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录一、异步调用方式分析1.1java8异步调用默认线程池方式二、使用自定义的线程池三、题外话,动态线程池3.1什么是动态线程池?3.2实践3.3动态线程池有什么意义?总结一、异步调

一、异步调用方式分析

今天在写代码的时候,想要调用异步的操作,这里我是用的java8的流式异步调用,但是使用过程中呢,发现这个异步方式有两个方法,如下所示:

区别是一个 需要指定线程池,一个不需要。

  • 那么指定线程池有哪些好处呢?直观的说有以下两点好处:

    • 可以根据我们的服务器性能,通过池的管理更好的规划我们的线程数。
    • 可以对我们使用的线程自定义名称,这里也是阿里java开发规范所提到的。

1.1 java8异步调用默认线程池方式

当然常规使用默认的也没什么问题。我们通过源码分析下使用默认线程池的过程。

   public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

看下这个asyncPool是什么?

如下所示,useCommonPool如果为真,就使用ForkJoinPool.commonPool(),否则创建一个new ThreadPerTaskExecutor()

    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

看看useCommonPool 是什么?

    private static final boolean useCommonPool =
        (ForkJoinPool.getCommonPoolParallelism() > 1);
  
    public static int getCommonPoolParallelism() {
        return commonParallelism;
    }

最终这个并行级别并没有给出默认值

static final int commonParallelism;

通过找到这个常量的调用,我们看看是如何进行初始化的,在ForkJoinPool中有一个静态代码块,启动时会对commonParallelism进行初始化,我们只关注最后一句话就好了,:

    // Unsafe mechanics
    private static final sun.misc.Unsafe U;
    private static final int  ABASE;
    private static final int  ASHIFT;
    private static final long CTL;
    private static final long RUNSTATE;
    private static final long STEALCOUNTER;
    private static final long PARKBLOCKER;
    private static final long QtOP;
    private static final long QLOCK;
    private static final long QSCANSTATE;
    private static final long QPARKER;
    private static final long QCURRENTSTEAL;
    private static final long QCURRENTJOIN;

    static {
        // initialize field offsets for CAS etc
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ForkJoinPool.class;
            CTL = U.objectFieldOffset
                (k.getDeclaredField("ctl"));
            RUNSTATE = U.objectFieldOffset
                (k.getDeclaredField("runState"));
            STEALCOUNTER = U.objectFieldOffset
                (k.getDeclaredField("stealCounter"));
            Class<?> tk = Thread.class;
            PARKBLOCKER = U.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            Class<?> wk = WorkQueue.class;
            QTOP = U.objectFieldOffset
                (wk.getDeclaredField("top"));
            QLOCK = U.objectFieldOffset
                (wk.getDeclaredField("qlock"));
            QSCANSTATE = U.objectFieldOffset
                (wk.getDeclaredField("scanState"));
            QPARKER = U.objectFieldOffset
                (wk.getDeclaredField("parker"));
            QCURRENTSTEAL = U.objectFieldOffset
                (wk.getDeclaredField("currentSteal"));
            QCURRENTJOIN = U.objectFieldOffset
                (wk.getDeclaredField("currentJoin"));
            Class<?> ak = ForkJoinTask[].class;
            ABASE = U.arrayBaseOffset(ak);
            int scale = U.arrayIndexScale(ak);
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }

        commonMaxSpares = DEFAULT_COMMON_MAX_SPARES;
        defaultForkJoinWorkerThreadFactory =
            new DefaultForkJoinWorkerThreadFactory();
        modifyThreadPermission = new RuntimePermission("modifyThread");

        common = java.security.AccessController.doPrivileged
            (new java.security.PrivilegedAction<ForkJoinPool>() {
                public ForkJoinPool run() { return makeCommonPool(); }});
         // 即使线程被禁用也是1,至少是个1
        int par = common.config & SMASK;
        commonParallelism = par > 0 ? par : 1;
    }

如下所示,默认是7:

所以接着下面的代码看:

    private static final boolean useCommonPool =
        (ForkJoinPool.getCommonPoolParallelism() > 1);

这里一定是返回true,证明当前是并行的。

    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

上面会返回一个大小是七的的默认线程池

其实这个默认值是当前cpu的核心数,我的电脑是八核,在代码中默认会将核心数减一,所以显示是七个线程。

        if (parallelism < 0 && //默认是1,小于核心数
            (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
            parallelism = 1;
        if (parallelism > MAX_CAP)
            parallelism = MAX_CAP;

下面我们写个main方法测试一下,10个线程,每个阻塞10秒,看结果:

    public static void main(String[] args) {
        // 创建10个任务,每个任务阻塞10秒
        for (int i = 0; i < 10; i++) {
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(10000);
                    System.out.println(new Date() + ":" + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

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

结果如下所示,前面七个任务先完成,另外三个任务被阻塞10秒后,才完成:

Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-5
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-4
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-2
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-7
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-3
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-6
Mon Sep 13 11:20:57 CST 2021:ForkJoinPool.commonPool-worker-1
-----------------------------------------------------------  
Mon Sep 13 11:21:07 CST 2021:ForkJoinPool.commonPool-worker-2
Mon Sep 13 11:21:07 CST 2021:ForkJoinPool.commonPool-worker-5
Mon Sep 13 11:21:07 CST 2021:ForkJoinPool.commonPool-worker-4

结论:当我们使用默认的线程池进行异步调用时,如果异步任务是一个IO密集型,简单说处理时间占用长,将导致其他使用共享线程池的任务阻塞,造成系统性能下降甚至异常。甚至当一部分调用接口时,如果接口超时,那么也会阻塞与超时时长相同的时间;实际在计算密集的场景下使用是能提高性能的。

二、使用自定义的线程池

上面说到如果是IO密集型的场景,在异步调用时还是使用自定义线程池比较好。

  • 针对开篇提到的两个显而易见的好处,此处新增一条:

    • 可以根据我们的服务器性能,通过池的管理更好的规划我们的线程数。
    • 可以对我们使用的线程自定义名称,这里也是阿里java开发规范所提到的。
    • 不会因为阻塞导致使用共享线程池的其他线程阻塞甚至异常。

我们自定义下面的线程池:


@Slf4j
public class GlobalThreadPool {

    
    public final static int CORE_POOL_SIZE = 10;

    
    public final static int MAX_NUM_POOL_SIZE = 20;

    
    public final static int BLOCKING_QUEUE_SIZE = 30;

    
    private final static ThreadPoolExecutor instance = getInstance();


    
    private synchronized static ThreadPoolExecutor getInstance() {
        // 生成线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_NUM_POOL_SIZE,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(BLOCKING_QUEUE_SIZE),
                new NamedThreadFactory("Thread-wjbgn-", false));
        return executor;
    }

    private GlobalThreadPool() {
    }

    public static ThreadPoolExecutor getExecutor() {
        return instance;
    }
}

调用:

    public static void main(String[] args) {
        // 创建10个任务,每个任务阻塞10秒
        for (int i = 0; i < 10; i++) {
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(10000);
                    System.out.println(new Date() + ":" + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },GlobalThreadPool.getExecutor());
        }

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

输出我们指定线程名称的线程:

Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-1
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-10
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-2
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-9
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-5
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-6
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-3
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-7
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-8
Mon Sep 13 11:32:35 CST 2021:Thread-Inbox-Model-4

三、题外话,动态线程池

3.1 什么是动态线程池?

在我们使用线程池的时候,是否有的时候很纠结,到底设置多大的线程池参数是最合适的呢?如果不够用了怎么办,要改代码重新部署吗?

其实是不需要的,记得当初看过美团的一篇文章,真的让人茅塞顿开啊,动态线程池。

ThreadPoolExecutor这个类其实是提供对于线程池的属性进行修改的,支持我们动态修改以下的属性:

从上至下分别是:

  • 线程工厂(用于指定线程名称)
  • 核心线程数
  • 最大线程数
  • 活跃时间
  • 拒绝策略。

在美团的文章当中呢,是监控服务器线程的使用率,当达到阈值就进行告警,然后通过配置中心去动态修改这些数值。

我们也可以这么做,使用@RefreshScope加Nacos就可以实现了。

3.2 实践

我写了一个定时任务,监控当前服务的线程使用率,小了就扩容,一段时间后占用率下降,就恢复初始值。

其实还有很多地方需要改进的,请大家多提意见,监控的是文章前面的线程池GlobalThreadPool,下面调度任务的代码:


@Slf4j
@Component
public class DaemonThreadTask {

    
    public final static int SERVER_MAX_SIZE = 50;

    
    private final static int MAXIMUM_THRESHOLD = 8;

    
    private final static int INCREMENTAL_MAX_NUM = 10;

    
    private final static int INCREMENTAL_CORE_NUM = 5;

    
    private static int currentSize = GlobalThreadPool.MAX_NUM_POOL_SIZE;

    
    private static int currentCoreSize = GlobalThreadPool.CORE_POOL_SIZE;

    @Scheduled(cron = "0 */5 * * * ?")
    public static void execute() {
        threadMonitor();
    }


    
    private static void threadMonitor() {
        ThreadPoolExecutor instance = GlobalThreadPool.getExecutor();
        int activeCount = instance.getActiveCount();
        int size = instance.getQueue().size();
        log.info("GlobalThreadPool: the active thread count is {}", activeCount);
        // 线程数不足,增加线程
        if (activeCount > GlobalThreadPool.MAX_NUM_POOL_SIZE % MAXIMUM_THRESHOLD
                && size >= GlobalThreadPool.BLOCKING_QUEUE_SIZE) {
            currentSize = currentSize + INCREMENTAL_MAX_NUM;
            currentCoreSize = currentCoreSize + INCREMENTAL_CORE_NUM;
            //当前设置最大线程数小于服务最大支持线程数才可以继续增加线程
            if (currentSize <= SERVER_MAX_SIZE) {
                instance.setMaximumPoolSize(currentSize);
                instance.setCorePoolSize(currentCoreSize);
                log.info("this max thread size is {}", currentSize);
            } else {
                log.info("current size is more than server max size, can not add");
            }
        }
        // 线程数足够,降低线程数,当前活跃数小于默认核心线程数
        if (activeCount < GlobalThreadPool.MAX_NUM_POOL_SIZE
                && size == 0
                && currentSize > GlobalThreadPool.MAX_NUM_POOL_SIZE) {
            currentSize = GlobalThreadPool.MAX_NUM_POOL_SIZE;
            currentCoreSize = GlobalThreadPool.CORE_POOL_SIZE;
            instance.setMaximumPoolSize(currentSize);
            instance.setCorePoolSize(currentCoreSize);
        }
    }
}

3.3 动态线程池有什么意义?

有的朋友其实问过我,我直接把线程池设置大一点不就好了,这种动态线程池有什么意义呢?

其实这是一个好问题。在以前的传统软件当中,单机部署,硬件部署,确实,我们能使用的线程数取决于服务器的核心线程数,而且基本没有其他服务来争抢这些线程。

但是现在是容器的时代,云原生的时代。

多个容器部署在一个宿主机上,那么当高峰期的时候,某个容器就需要占用大量的cpu资源,如果所有的容器都将大部分资源占据,那么这个容器必然面临阻塞甚至瘫痪的风险。

当高峰期过了,释放这部分资源可以被释放掉,用于其他需要的容器。。

再结合到目前的云服务器节点扩容,都是需要动态扩缩容的的,和线程相似,在满足高可用的情况下,尽量的节约成本。

总结

到此这篇关于java8异步调用如何使用的文章就介绍到这了,更多相关java8异步调用使用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: java8异步调用如何使用才是最好的方式

本文链接: https://www.lsjlt.com/news/164007.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java8异步调用如何使用才是最好的方式
    目录一、异步调用方式分析1.1java8异步调用默认线程池方式二、使用自定义的线程池三、题外话,动态线程池3.1什么是动态线程池?3.2实践3.3动态线程池有什么意义?总结一、异步调...
    99+
    2024-04-02
  • Java8如何使用CompletableFuture构建异步应用方式
    小编给大家分享一下Java8如何使用CompletableFuture构建异步应用方式,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!概述为了展示 Completa...
    99+
    2023-06-25
  • Java8 使用CompletableFuture 构建异步应用方式
    目录概述同步API VS 异步API同步API异步API同步的困扰实现异步API将同步方法改为异步方法处理异常错误概述 为了展示 CompletableFuture 的强大特性, 创...
    99+
    2024-04-02
  • Vue中的同步调用和异步调用方式
    目录Vue的同步调用和异步调用Promise实现异步调用async /await方法实现同步调用Vue同步和异步的问题基本语法实例Vue的同步调用和异步调用 Promise实现异步调...
    99+
    2024-04-02
  • Spring中的如何使用@Async异步调用
    这篇文章主要介绍了Spring中的如何使用@Async异步调用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用@Async异步调用方法Async简介异步方法调用使用场景:处...
    99+
    2023-06-25
  • stream_socket_client的异步使用方式
    函数的基本介绍 PHP 5、7、8,stream_socket_client用于建立网络或IPC socket连接。函数的参数和返回为: stream_socket_client( string $address, int &$...
    99+
    2023-09-10
    网络
  • php中如何异步调用方法
    php中异步调用方法的操作步骤在返回客户端的html代码中,嵌入ajax调用或者嵌入一个img标签,src指向要执行的耗时脚本。使用popen函数打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。使用CURL,设置CU...
    99+
    2024-04-02
  • 如何使用Promise链式调用解决多个异步回调的问题
    小编给大家分享一下如何使用Promise链式调用解决多个异步回调的问题,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!介绍所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束...
    99+
    2024-04-02
  • Spring中的使用@Async异步调用方法
    目录使用@Async异步调用方法Async简介给Spring的TaskExecutor去完成本次记录Async使用场景异步请求与异步调用的区别异步请求的实现SpringBoot中异步...
    99+
    2024-04-02
  • Java8中Stream的使用方式是什么
    这篇文章主要介绍“Java8中Stream的使用方式是什么”,在日常操作中,相信很多人在Java8中Stream的使用方式是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java8中Stream的使用方式...
    99+
    2023-06-30
  • 说说Java异步调用的几种方式
    目录一、通过创建新线程二、通过线程池三、通过@Async注解四、通过CompletableFuture日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异...
    99+
    2024-04-02
  • Java异步调用的方法是什么
    这篇文章主要讲解了“Java异步调用的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java异步调用的方法是什么”吧!一、创建线程@Testpublic void&nbs...
    99+
    2023-06-27
  • python异步调用shell的方法是什么
    在Python中,可以使用`subprocess`模块来创建和管理子进程,以及执行外部命令。`subprocess`模块提供了多种方...
    99+
    2023-09-22
    python shell
  • Spring使用ThreadPoolTaskExecutor自定义线程池及异步调用方式
    目录一、ThreadPoolTaskExecutor1、将线程池用到的参数定义到配置文件中2、Executors的工厂配置2.1、配置详情2.2、注解说明2.3、线程池配置说明2.4...
    99+
    2024-04-02
  • java接口异步调用的方法是什么
    在Java中,可以使用以下几种方法来实现接口的异步调用: 使用回调函数(Callback):在接口中定义一个回调方法,然后在调用方...
    99+
    2023-10-25
    java
  • java 如何快速实现异步调用方法
    java 如何快速实现异步调用方法 什么是异步编程CompletableFuturejava 演示 什么是异步编程 在实现异步调用之前,我们先了解一下,什么是异步编程?什么场景下适用等等情况。 我们都知道,在传统的同步编程中...
    99+
    2023-08-17
    java 微服务 多线程 异步编程 云原生 原力计划
  • 在spring boot中如何使用@Async实现异步调用
    在spring boot中如何使用@Async实现异步调用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。什么是”异步调用”与”同步调用”“同步调用”就是程序按照一定的顺序依次执...
    99+
    2023-05-31
    springboot @async 异步调用
  • 如何使用PHP编程实现高效的异步API调用?
    PHP编程是当今应用最广泛的编程语言之一,它不仅具有简单易学、语法简洁、易于维护等优点,还具有高效、灵活、可扩展等优势。在实现API调用时,PHP提供了一些非常方便的工具和技术,可以帮助我们实现高效的异步API调用。本文将介绍如何使用PHP...
    99+
    2023-10-29
    编程算法 api 异步编程
  • 使用IntelliJIDEA调式Stream流的方法步骤
    目录前言开篇在使用Stream的地方打个断点 Debug方式运行前言 今天有小伙伴问我Stream流该怎么调式的问题,在跟他讲了之后我觉得有必要分享给各位小伙伴这个调式技巧...
    99+
    2024-04-02
  • SpringBoot整合Quartz及异步调用的方法是什么
    这篇文章主要介绍“SpringBoot整合Quartz及异步调用的方法是什么”,在日常操作中,相信很多人在SpringBoot整合Quartz及异步调用的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作