Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑

这篇具有很好参考价值的文章主要介绍了Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.前言

1.1Dromara致力于微服务云原生解决方案的组织

​   在介绍Hippo4j和DynamicTp动态线程池之前有必要介绍下一个咱们国人的一个开源组织:Dromara致力于微服务云原生解决方案的组织,组织的地址如下:

https://dromara.org/zh/

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

  这个口号:为往圣继绝学,一个人或许能走的更快,但一群人会走的更远愿景:让每一位开源爱好者,体会到开源的快乐是我比较喜欢的,至于开放里面的兼容性,这个我就不太喜欢了,在使用Hippo4j和DynamicTp两款动态线程池的框架的时候就发现这个问题是在是让人头大,开源的东西一般都是这种兼容性不是那么好,发现框架bug要么不用要么给官方提issues要么自己上手调试改源码。

​ Dromara开源社区的一些优秀项目如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

​   点击官方首页的快速开始,里面还有好多优秀的开源的项目。

1.2 动态线程池的思路

​    动态线程池的思路基本都是来自美团的动态线程池的设计思想,链接如下:

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

​    Java线程池可以动态的参数就只有一下几个,无非就是搞一个界面可视化配置动态刷新、监控,告警、适配第三方框架使用的线程池、JDK原生的线程池等等,基本都是这种玩的:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

1.3Hippo4j和DynamicTp动态线程池解决什么痛点

​    简单来讲就是要达到一个可视化、可动态、可观测和可实时监控告警的效果,为业务测使用了线程池保驾护航,让线上业务平稳运行,不至于由于各种线程池的参数设置不恰当在大流量的冲击下而无法轻松应对,需要根据业务请求的体量来调整线程池各种参数原先基本都需要改代码然后需要部署重启项目,这个可以说是不方便也不快捷和高效,有了动态线程接入即可轻松可视化配置管理各种线程池,动态修改实时感知刷新都不需要重启和修改业务代码等操作,这两个动态线程池的实现可以说是大同小异,各有千秋-----道都是相同的,只不过是术不一样而已。

2.介绍

2.1Hippo4j的官网如下

https://hippo4j.cn/

2.2DynamicTp的官网如下

https://dynamictp.cn/

2.3Hippo4j的架构

2.3.1架构

Hippo4j 从部署的角度上分为两种角色:Server 端和 Client 端。

Server 端是 Hippo4j 项目打包出的 Java 进程,功能包括用户权限、线程池监控以及执行持久化的动作。

Client 端指的是我们 SpringBoot 应用,通过引入 Hippo4j Starter Jar 包负责与 Server 端进行交互。

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.3.2运行模式

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.3.3server控制台

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

控制台登录页面

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.4DynamicTp的架构

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.5Hippo4j的工程目录结构

  工程目录结构官网都有相应的说明,看工程模块的名字也大致可以猜测是啥功能的:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

  这个是Hippo4j的开发分支的代码,如果你下载的是gitHub上的Hippo4j1.4.3的源码包会跑不起来,因为官方在移除common-lang3的依赖的时候出问题了,hipp4j-common的模块中缺少依赖,之前我下载Hippo4j1.4.3的源码包运行也是缺少包,后面在作者的一个群里问那个作者,他说让我拉取开发分支,然后我拉取了也是一样的问题,所以解决办法就是在hipp4j-common的模块中加入入下的依赖:

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.objenesis/objenesis -->
        <dependency>
            <groupId>org.objenesis</groupId>
            <artifactId>objenesis</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

2.6DynamicTp的工程目录结构

  工程目录结构官网都有相应的说明,看工程模块的名字也大致可以猜测是啥功能的:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

  编译需要注意的问题:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.7编译禁用测试

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

2.8二者的异同

  相同点自然不用说了,都是实现动态线程池,拒绝策略的选择都是用的是JDK的接口动态代理,大致有如下不同,还有其它的不同有待大家去探索发现:

  1. DynamicTp集成了阿里开源的TTL:TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。
  2. Hippo4j有自己的server控制台可以替代nacos等开源配置中心
  3. 二者适配第三方线程池有所不同
  4. 二者的拒绝策略除了JDK原生的拒绝策略之外,还有各自的扩展实现
  5. Hippo4j日志可以接入ES6.x
  6. 二者依赖的第三方的配置中心支持有所不同

3.使用中遇到的坑

3.1 本地环境搭建

  参看我之前写的博客:

https://mp.weixin.qq.com/s/PQ8PQ3GfW_mLqmzUmxAHSQ

3.2 demo实例

​    由于我只写了Hippo4j的集成的demo的实例,但是在这个demo的版本下Hippo4j和DynamicTp都会遇到相同的关于nacos的动态刷新的问题,为此我还给nacos和springCloudAlibaba都提了一个issue,本来之前是只给nacos官方提了一个nacos的,后面nacos官方开发回复让我第一个问题转springCloudAlibaba提issue,所以我又去springCloudAlibaba提了一个issue:

3.2.1nacos提的isues地址如下

https://github.com/alibaba/nacos/issues/10139

3.2.2springCloudAlibaba提issue地址如下

https://github.com/alibaba/spring-cloud-alibaba/issues/3217

3.2.3demo分享地址

链接:https://pan.baidu.com/s/1-xkNUK-80w1Vf9DE9wcRog 
提取码:fji5

分享内容说明:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

3.3demo版本

springBoot版本:2.3.12.RELEASE
spring-cloud版本:Hoxton.SR12
spring-cloud-alibaba版本:2.2.9.RELEASE
nacos的服务端和客户端2.1.1
hippo4j-config-spring-boot-starter版本:1.4.3-upgrade

springCloudAlibaba官方的版本对应关系图:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

3.4问题的根本原因

3.4.1Hippo4j的SpingClouAlibaba的刷新配置的监听器配置如下

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

3.4.2DynamicTp的SpingClouAlibaba的刷新配置的监听器配置如下

DynamicTp的SpingClouAlibaba的刷新配置依赖了nacos的这个刷新配置的自动装配的(估计)

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

3.4.3SpingClouAlibaba的NacosContextRefresher监听器代码如下

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

3.4.4 根本原因有两个

  1. spring-cloud-alibaba版本:2.2.9.RELEASE的这个版本的扩展配置和共享配置更新不会热更新,只有项目主配置文件在nacos后台配置修改会发生热更新

  2. Hippo4j和DynamicTp的SpingClouAlibaba的刷新配置的自动装配是直接在装配适配类的 afterPropertiesSet()方法中直接加入的,由于这个spring-cloud-alibaba版本2.2.9.RELEASE的这个版本是匿名内部类的方式加入的配置变革的监听器,所以Hippo4j和DynamicTp的方式加入的监听器无效,由于nacos2.1.1的客户端使用的是grpc的方式,nacos2.0以下使用的是http长链接的方式,nacos客户端都用一个ClientWork有一个线程以一定的时间间隔去拉取服务端的配置变更,如果 开启了服务端变更推送的话,在nacos服务端改了配置客户端立马就可以感知到变化,然后拉取变更的配置信息和上一次本地的配置信息的md5去比较,如果两次的md5的值不同,说明配置变更了,会触发cacheData的safeNotifyListener(),如下代码是nacos2.1.1的客户端代码,如果是nacos1.4.1的会有所有不同,但是大致的流程是这种的:

      void checkListenerMd5() {
            for (ManagerListenerWrap wrap : listeners) {
                if (!md5.equals(wrap.lastCallMd5)) {
                    safeNotifyListener(dataId, group, content, type, md5, encryptedDataKey, wrap);
                }
            }
        }
    

    safeNotifyListener方法如下:

    private void safeNotifyListener(final String dataId, final String group, final String content, final String type,
                final String md5, final String encryptedDataKey, final ManagerListenerWrap listenerWrap) {
            final Listener listener = listenerWrap.listener;
            if (listenerWrap.inNotifying) {
                LOGGER.warn(
                        "[{}] [notify-currentSkip] dataId={}, group={}, md5={}, listener={}, listener is not finish yet,will try next time.",
                        name, dataId, group, md5, listener);
                return;
            }
            Runnable job = () -> {
                long start = System.currentTimeMillis();
                ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
                ClassLoader appClassLoader = listener.getClass().getClassLoader();
                try {
                    if (listener instanceof AbstractSharedListener) {
                        AbstractSharedListener adapter = (AbstractSharedListener) listener;
                        adapter.fillContext(dataId, group);
                        LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
                    }
                    // Before executing the callback, set the thread classloader to the classloader of
                    // the specific webapp to avoid exceptions or misuses when calling the spi interface in
                    // the callback method (this problem occurs only in multi-application deployment).
                    Thread.currentThread().setContextClassLoader(appClassLoader);
                    
                    ConfigResponse cr = new ConfigResponse();
                    cr.setDataId(dataId);
                    cr.setGroup(group);
                    cr.setContent(content);
                    cr.setEncryptedDataKey(encryptedDataKey);
                    configFilterChainManager.doFilter(null, cr);
                    String contentTmp = cr.getContent();
                    listenerWrap.inNotifying = true;
                    listener.receiveConfigInfo(contentTmp);//这里就是触发监听器回调的钩子方法,这两个动态线程池的nacos的动态刷新就是利用了这个个点实现了动态的效果
                    // compare lastContent and content
                    if (listener instanceof AbstractConfigChangeListener) {
                        Map<String, ConfigChangeItem> data = ConfigChangeHandler.getInstance()
                                .parseChangeData(listenerWrap.lastContent, contentTmp, type);
                        ConfigChangeEvent event = new ConfigChangeEvent(data);
                        ((AbstractConfigChangeListener) listener).receiveConfigChange(event);
                        listenerWrap.lastContent = contentTmp;
                    }
                    
                    listenerWrap.lastCallMd5 = md5;
                    LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ,cost={} millis.", name, dataId,
                            group, md5, listener, (System.currentTimeMillis() - start));
                } catch (NacosException ex) {
                    LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", name,
                            dataId, group, md5, listener, ex.getErrCode(), ex.getErrMsg());
                } catch (Throwable t) {
                    LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={}", name, dataId, group, md5,
                            listener, t);
                } finally {
                    listenerWrap.inNotifying = false;
                    Thread.currentThread().setContextClassLoader(myClassLoader);
                }
            };
            
          ............................
              ............................
        }
    

3.我想说这个两个项目都是在一个开源组织下面的,那么这两个作者估计都认识,看着代码风格有点相似,里面好多的代码看着都很相的,不会是这两个作者相互照抄的吧?不对,说错了,这个应该说是相互借鉴才会对的,哈哈!

3.5demo运行和验证

​    demo运行起来是像这种的,不会报错的,如果跑起来就挂了绝大部分原因是因为网络的问题,注意:要使用本地环境来跑的配置,使用上面给那个本地环境搭建的方法来搭建本地环境最好不过,然后使用本文提供的demo来跑,我已经试过了这种修复这个问题是OK的

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

验证:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

保存发布后访问控制器,发现message-consume的核心线程数为28,已经变更了:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

4.解决办法

​    修改spring-cloud-alibaba版2.2.9.RELEASE的NacosContextRefresher类,在这个类里面匹配到Hippo4j和DynamicTp配置所使用的主配置yaml的data-id和group,然后去触发各自适配类的动态刷新的逻辑即可,修改代码如下所示:

/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.nacos.refresh;

import cn.hippo4j.common.config.ApplicationContextHolder;
import cn.hippo4j.config.springboot.starter.config.BootstrapConfigProperties;
import cn.hippo4j.config.springboot.starter.refresher.NacosCloudRefresherHandler;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosPropertySourceRepository;
import com.alibaba.cloud.nacos.client.NacosPropertySource;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.endpoint.event.RefreshEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * On application start up, NacosContextRefresher add nacos listeners to all application
 * level dataIds, when there is a change in the data, listeners will refresh
 * configurations.
 *
 * @author juven.xuxb
 * @author pbting
 */
public class NacosContextRefresher
        implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

    private final static Logger log = LoggerFactory
            .getLogger(NacosContextRefresher.class);

    private static final AtomicLong REFRESH_COUNT = new AtomicLong(0);

    private NacosConfigProperties nacosConfigProperties;

    private final boolean isRefreshEnabled;

    private final NacosRefreshHistory nacosRefreshHistory;

    private final ConfigService configService;

    private ApplicationContext applicationContext;

    private AtomicBoolean ready = new AtomicBoolean(false);

    private Map<String, Listener> listenerMap = new ConcurrentHashMap<>(16);

    private BootstrapConfigProperties bootstrapConfigProperties;

    private NacosCloudRefresherHandler nacosCloudRefresherHandler;


    public NacosContextRefresher(NacosConfigManager nacosConfigManager,
                                 NacosRefreshHistory refreshHistory) {
        this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
        this.nacosRefreshHistory = refreshHistory;
        this.configService = nacosConfigManager.getConfigService();
        this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled();
        this.bootstrapConfigProperties = ApplicationContextHolder.getBean(BootstrapConfigProperties.class);
        this.nacosCloudRefresherHandler = ApplicationContextHolder.getBean(NacosCloudRefresherHandler.class);
    }

    /**
     * recommend to use
     * {@link NacosContextRefresher#NacosContextRefresher(NacosConfigManager, NacosRefreshHistory)}.
     *
     * @param refreshProperties refreshProperties
     * @param refreshHistory    refreshHistory
     * @param configService     configService
     */
    @Deprecated
    public NacosContextRefresher(NacosRefreshProperties refreshProperties,
                                 NacosRefreshHistory refreshHistory, ConfigService configService) {
        this.isRefreshEnabled = refreshProperties.isEnabled();
        this.nacosRefreshHistory = refreshHistory;
        this.configService = configService;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // many Spring context
        if (this.ready.compareAndSet(false, true)) {
            this.registerNacosListenersForApplications();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * register Nacos Listeners.
     */
    private void registerNacosListenersForApplications() {
        if (isRefreshEnabled()) {
            for (NacosPropertySource propertySource : NacosPropertySourceRepository
                    .getAll()) {
                if (!propertySource.isRefreshable()) {
                    continue;
                }
                String dataId = propertySource.getDataId();
                String group = propertySource.getGroup();
                registerNacosListener(group, dataId);
            }
        }
    }

    private void registerNacosListener(final String groupKey, final String dataKey) {
        String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
        Listener listener = listenerMap.computeIfAbsent(key,
                lst -> new AbstractSharedListener() {
                    @Override
                    public void innerReceive(String dataId, String group,
                                             String configInfo) {
                        refreshCountIncrement();
                        nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
                        applicationContext.publishEvent(
                                new RefreshEvent(this, null, "Refresh Nacos config"));
                        if (log.isDebugEnabled()) {
                            log.debug(String.format(
                                    "Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
                                    group, dataId, configInfo));
                        }
                        //这里匹配到对应的配置然后触发各自的动态刷新的逻辑即可
                        Map<String, String> nacosConfig = bootstrapConfigProperties.getNacos();
                        String dataId1 = nacosConfig.get("data-id");
                        String group1 = nacosConfig.get("group");
                        if (dataId1.equals(dataId) && group1.equals(group)) {
                            nacosCloudRefresherHandler.dynamicRefresh(configInfo);
                        }
                    }
                });
        try {
            configService.addListener(dataKey, groupKey, listener);
        } catch (NacosException e) {
            log.warn(String.format(
                    "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
                    groupKey), e);
        }
    }

    public NacosConfigProperties getNacosConfigProperties() {
        return nacosConfigProperties;
    }

    public NacosContextRefresher setNacosConfigProperties(
            NacosConfigProperties nacosConfigProperties) {
        this.nacosConfigProperties = nacosConfigProperties;
        return this;
    }

    public boolean isRefreshEnabled() {
        if (null == nacosConfigProperties) {
            return isRefreshEnabled;
        }
        // Compatible with older configurations
        if (nacosConfigProperties.isRefreshEnabled() && !isRefreshEnabled) {
            return false;
        }
        return isRefreshEnabled;
    }

    public static long getRefreshCount() {
        return REFRESH_COUNT.get();
    }

    public static void refreshCountIncrement() {
        REFRESH_COUNT.incrementAndGet();
    }

}

demo中代码修改如下图所示:

Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑,java,云原生,微服务

5.总结

​    这种开源框架使用确实会遇到各种各样的问题,但是不要害怕、不要灰心,办法总比困难多,联系作者提issue或者联系官方提issue,都不得行的话就只能自己debug然后找问题改源码了,上面的demo只是一个影子,至于什么监控搭建和通知啥的这个就靠大家自己去探索了,我觉得那些都是次要的,主要的就是要能实现动态管理和配置,说白了,如果没有这些动态开源的框架出现,我们的操作还是可以一波梭哈,完全没有这些花里胡哨的东西,只不过是那些大佬觉麻烦,需要偷懒,所以才搞了这种骚操作,但是可以从这些开源实现中学习到一些东西的,如果我的分享对你有帮助,请一键三连,么么么哒!文章来源地址https://www.toymoban.com/news/detail-599088.html

到了这里,关于Hippo4j和DynamicTp动态线程池介绍和使用中遇到的坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java中线程池的使用及原理(附带线程池拒绝策略介绍)

    目录 1.使用线程池的目的 2.线程池的使用及原理 3.生动讲解拒绝策略: 4.自己实现一个简单的线程池 5.线程数的决定方法 6.总结 希望各位大佬能多多点赞关注收藏,有了你们的支持,我也能更有动力的向你们学习不是(手动狗头)!!! 在知道线程池之前,每当我们需要一

    2024年04月17日
    浏览(29)
  • CountDownLatch介绍和使用【Java多线程必备】

    点击   Mr.绵羊的知识星球  解锁更多优质文章。 目录 一、介绍 二、特性 三、实现原理 四、适用场景 五、注意事项 六、实际应用     CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可

    2024年02月05日
    浏览(40)
  • 线程池的介绍、原理、监控运维、框架使用场景案例

    线程池是一种线程复用的技术,它可以有效地控制线程的数量,处理过程中将任务添加到队列,然后在线程创建后启动这些任务。主要作用有: 重用线程,减少线程创建和销毁带来的开销。 可以有效控制线程的数量,方便线程管理。 提高系统响应速度,当有新的任务到来时,无需等待

    2024年02月03日
    浏览(30)
  • Java 高级应用-多线程-(四)FutureTask的介绍及使用

    Java多线程之FutureTask的介绍及使用 FutureTask属于java.util.concurrent 包;FutureTask表示可取消的异步计算。FutureTask类提供了一个Future的基本实现 ,具有启动和取消计算的方法,查询计算是否完整,并检索计算结果。结果只能在计算完成后才能检索; 如果计算尚未完成,则get方法将阻

    2024年02月07日
    浏览(27)
  • 【Java项目】使用Nacos实现动态线程池技术以及Nacos配置文件更新监听事件

    真诚的希望能给我项目一个stars!!! 项目源码 项目视频演示 线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如Tomcat。 线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性

    2024年02月09日
    浏览(26)
  • 【Java中的Thread线程的简单方法介绍和使用详细分析】

    提示:若对Thread没有基本的了解,可以先阅读以下文章,同时部分的方法已经在如下两篇文章中介绍过了,本文不再重复介绍!! 【Java中Tread和Runnable创建新的线程的使用方法】 【Java中的Thread线程的七种属性的使用和分析】 提示:以下是本篇文章正文内容,下面案例可供参

    2024年02月15日
    浏览(30)
  • router4j--SpringCloud动态路由利器

    本文介绍Java的动态路由中间件:router4j。router4j用于SpringCloud项目,它可以将某个url请求路由到指定的机器上,也可以将所有请求强制转到指定机器。 问题描述 Java后端在开发SpringCloud项目时如果同一个应用起了多个实例,会遇到以下问题: 无法将指定url请求强制转到个人电脑

    2024年01月25日
    浏览(24)
  • Knife4j框架介绍

    Knife4j是一款基于Swagger 2的在线API文档框架,是日常开发中很常用的框架,基于此框架,后端可以和前端开发人员进行高效沟通。 使用Knife4j框架只需要以下三步: 1:添加Knife4j的依赖 (当前建议使用的Knife4j版本,只适用于Spring Boot 2.6以下版本,不含Spring Boot 2.6) 2:在主配置

    2024年02月13日
    浏览(23)
  • 微信小程序slot插槽的介绍,以及如何通过uniapp使用动态插槽

    微信小程序文档 - slots介绍 由上述文档看俩来,微信小程序官方并没有提及动态插槽内容。 uniapp文档 - slots介绍 uni官方也未提及关于动态插槽的内容 在实际使用中,直接通过 slot :name=\\\"item.xxx\\\" / 这种形式会报错, 网上搜了大量资料发现只能通过条件编译的方式  下面是兼容微

    2024年02月07日
    浏览(30)
  • Neo4j的基本介绍

    1、简介 NoSQL数据库四大家族 列存储 Hbase,键值(Key-Value)存储 Redis,图像存储 Neo4j,文档存储 MongoDB。 Neo4j是以原生图形数据库为核心,以更自然的连接状态存储和管理数据。 是用 Java 和 Scala 编写的, 图数据库采用属性图方式,对遍历性能和操作运行时间都有好处。 应用场景 1

    2024年02月04日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包