基于ThreadPoolExecutor实现动态线程池

news2024/9/22 19:37:13

项目上,我们是根据业务的使用频率,基于ThreadPoolExecutor自定义一个线程池,线程池的核心线程数、最大线程数、阻塞队列的容量都是估计的设置的,但是不知道线程资源的真正使用情况。

1.ThreadPoolExecutor配置参数动态修改

先来看看JDK的ThreadPoolExecutor是如何支持动态设置线程池参数的。ThreadPoolExecutor主要支持两个参数的修改,一个是corePoolSize核心线程数,另一个是maximumPoolSize最大线程数。先写个简单的Demo,熟悉下着两个参数的使用。

(1)定义一个线程池

注意这里每次调用getThreadPoolExecutor方法都应该返回同一个线程池,一开始,我是每次都new一个新的对象,导致后面修改线程池参数不生效。

核心线程数:1
最大线程数:2
阻塞队列容量:1
拒绝策略:任务溢出,直接中止,抛出异常。
最多同时并发执行3个任务。

import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ThreadUtil {

    private static ThreadPoolExecutor threadPoolExecutor;

    public static ThreadPoolExecutor getThreadPoolExecutor() {
        if(null == threadPoolExecutor) {
            threadPoolExecutor = new ThreadPoolExecutor(1, 2, 10, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy());
        }

        return threadPoolExecutor;
    }
}

(2)线程池执行任务

我这里定义了一个接口TestRunnable,继承Callable接口,定义类TestRunnableImpl实现TestRunnable接口,这里多此一举的原因是,ThreadPoolExecutor的invokeAll方法需要调用的是继承Callable接口的子类。

我先是用jmeter做并发测试,哪怕1秒达到100个任务,但是根据打印日志显示,都是只用了一个线程,并且请求都能正常执行,不会出现任务超过(最大线程数+队列容量),然后抛出异常的情况。我猜测是线程执行任务很快(CPU上下文切换快),jmeter只是做到了http请求并发,但是把任务交给ThreadPoolExecutor执行未必并行。

ThreadPoolExecutor的invokeAll多个任务,可以得到预期效果。

/executeRunnable 请求加了一个index入参是为了控制同时执行的任务数。

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import gdut.ThreadUtil;
import gdut.entity.Article;
import gdut.entity.ArticleParams;
import gdut.entity.UpdateThreadParam;
import gdut.entity.UserLog;
import gdut.mapper.ArticleMapper;
import gdut.service.ArticleService;
import gdut.service.UserLogService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;

@RestController
@RequestMapping("/thread")
@Slf4j
public class ThreadController {

    @Autowired
    private ArticleService articleService;

    @Autowired
    private UserLogService userLogService;

    @PostMapping("/executeRunnable")
    public void executeRunnable(@RequestParam("index") int index) {
        ThreadPoolExecutor threadPoolExecutor = ThreadUtil.getThreadPoolExecutor();
        List<TestRunnableImpl> callableList = Lists.newArrayList();
        for (int i = 0; i < index; i++) {
            callableList.add(new TestRunnableImpl());
        }
        try {
            threadPoolExecutor.invokeAll(callableList);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("active - {} - {} - {} - {} - {}", threadPoolExecutor.getActiveCount(), threadPoolExecutor.getCorePoolSize(),
                threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getTaskCount(), threadPoolExecutor.getCompletedTaskCount());
    }

    interface TestRunnable<String> extends Callable<String> {

    }

    @AllArgsConstructor
    class TestRunnableImpl implements TestRunnable<String> {

        @Override
        public String call() {
            log.info("test  - {} - {}", Thread.currentThread().getName(), LocalDateTime.now().toString());
            List<Article> articleList = articleService.list();
            long count = userLogService.count();
            log.info("end  - {} - {}", Thread.currentThread().getName(), LocalDateTime.now().toString());
            return "success";
        }

    }
}

测试结果:

a.当任务数设置为3

http://localhost:8089/thread/executeRunnable?index=3

从打印日志可以看出,这里是创建了2个线程执行任务,符合最大线程数。
在这里插入图片描述
b.当任务数设置为4

http://localhost:8089/thread/executeRunnable?index=4

抛出异常:java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@151789c9 rejected from java.util.concurrent.ThreadPoolExecutor@261c83ca[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 3]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) ~[na:1.8.0_40]
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) [na:1.8.0_40]
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) [na:1.8.0_40]

(3)修改线程池参数

 @PostMapping("/updateThreadPoolExecutor")
public String updateThreadPoolExecutor(@RequestBody UpdateThreadParam updateThreadParam) {
    ThreadPoolExecutor threadPoolExecutor = ThreadUtil.getThreadPoolExecutor();
    threadPoolExecutor.setCorePoolSize(updateThreadParam.getCoreSize());
    threadPoolExecutor.setMaximumPoolSize(updateThreadParam.getMaxSize());
    return "success";
}

增加1个核心线程和最大线程数。

http://localhost:8089/thread/updateThreadPoolExecutor

{
    "coreSize":2,
    "maxSize":3
}

再次下发http://localhost:8089/thread/executeRunnable?index=4 可以正常运行。

2.动态线程池实现

为了能更直观查看线程池运行时参数配置和执行情况,建立两个数据库表,一个是线程池参数配置表,另一个是线程执行记录表。

CREATE TABLE `thread_pool_config`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `thread_pool_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '线程池名称',
  `core_thread_size` int(0) NULL DEFAULT NULL COMMENT '核心线程数',
  `maximum_thread_size` int(0) NULL DEFAULT NULL COMMENT '最大线程数',
  `queue_capacity` int(0) NULL DEFAULT NULL COMMENT '队列容量',
  `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '当前时间',
  `update_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  `enable_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否有效',
  `pool_size` int(0) NULL DEFAULT NULL COMMENT '任务数量',
  `active_threads` int(0) NULL DEFAULT NULL COMMENT '活跃执行任务线程数',
  `queue_tasks` int(0) NULL DEFAULT NULL COMMENT '队列任务数量',
  `completed_tasks` int(0) NULL DEFAULT NULL COMMENT '完成任务总数',
  PRIMARY KEY (`id`) USING BTREE
)

CREATE TABLE `thread_log`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `thread_pool_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '线程池名称',
  `thread_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '线程名称',
  `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '当前时间',
  PRIMARY KEY (`id`) USING BTREE
)

创建默认线程池和动态修改线程池配置,把创建过的线程池放入Map容器中,后面可以从这个Map容器获取创建过的全部线程池。创建默认线程池,我这里根据线程池名称做了一个双层检查锁,确保不会重复创建新对象。

import lombok.Data;

import java.io.Serializable;

@Data
public class DynamicThreadPoolParams implements Serializable {

    private String threadPoolName;

    private int corePoolSize;

    private int maximumPoolSize;

    private int queueCapacity;
}

import gdut.entity.DynamicThreadPoolParams;
import jodd.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class DynamicThreadPoolExecutor {

    public static final int DEFAULT_CORE_POOL_SIZE = 5;
    public static final int DEFAULT_MAXIMUM_POOL_SIZE = 10;
    public static final int QUEUE_CAPACITY = 20;

    //key是线程池名称,value是线程池
    public static Map<String, ThreadPoolExecutor> executorPool = new HashMap();

    public static Map<String, ThreadPoolExecutor> getExecutorPool() {
        return executorPool;
    }

    public static ThreadPoolExecutor dynamicThreadPoolExecutor(DynamicThreadPoolParams dynamicThreadPoolParams) {
        ThreadPoolExecutor executorByName = executorPool.get(dynamicThreadPoolParams.getThreadPoolName());
        if(null != executorByName) {
            executorByName.setCorePoolSize(dynamicThreadPoolParams.getCorePoolSize());
            executorByName.setMaximumPoolSize(dynamicThreadPoolParams.getMaximumPoolSize());
            return executorByName;
        }

        synchronized (executorPool) {
            if(null != executorByName) {
                executorByName.setCorePoolSize(dynamicThreadPoolParams.getCorePoolSize());
                executorByName.setMaximumPoolSize(dynamicThreadPoolParams.getMaximumPoolSize());
                return executorByName;
            }

            //创建线程工厂
            ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNameFormat(dynamicThreadPoolParams.getThreadPoolName()).get();
            executorByName = new ThreadPoolExecutor(dynamicThreadPoolParams.getCorePoolSize(), dynamicThreadPoolParams.getMaximumPoolSize(), 60, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(dynamicThreadPoolParams.getQueueCapacity()), threadFactory, new ThreadPoolExecutor.AbortPolicy());
            executorPool.put(dynamicThreadPoolParams.getThreadPoolName(), executorByName);
        }

        return executorByName;
    }

    /**
     * 默认线程池
     * @param threadPoolName 线程池名称
     * @return
     */
    public static ThreadPoolExecutor defaultThreadPoolExecutor(String threadPoolName) {
        //先从Map容器获取,获取到直接返回,没有获取到,双重检查
        ThreadPoolExecutor executorByName = executorPool.get(threadPoolName);
        if(null != executorByName) {
            return executorByName;
        }

        synchronized (executorPool) {
            if(null == executorByName) {
                //创建线程工厂
                ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNameFormat(threadPoolName + "-%d").get();
                executorByName = new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAXIMUM_POOL_SIZE, 60, TimeUnit.SECONDS,
                        new ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY), threadFactory, new ThreadPoolExecutor.AbortPolicy());
                executorPool.put(threadPoolName, executorByName);
            }
        }

        return executorByName;
    }

}

多线程调用的时候,向线程执行日志表写入数据。这里加一个30秒的等待时间,是为了可以在这30秒,有时间观察到线程池创建的核心线程数、最大线程数、以及队列中等待的任务数量。

import gdut.entity.Article;
import gdut.entity.ThreadLog;
import gdut.service.ArticleService;
import gdut.service.ThreadLogService;
import gdut.service.UserLogService;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.Callable;

@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Setter
public class TestRunnableImpl implements TestRunnable<String> {

    private ArticleService articleService;
    private UserLogService userLogService;
    private ThreadLogService threadLogService;
    private String threadPoolName;

    @Override
    public String call() {
        log.info("test  - {} - {}", Thread.currentThread().getName(), LocalDateTime.now().toString());
        List<Article> articleList = articleService.list();
        long count = userLogService.count();
        try {
            Thread.sleep(30 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        saveThreadLog();
        log.info("end  - {} - {}", Thread.currentThread().getName(), LocalDateTime.now().toString());
        return "success";
    }

    public void saveThreadLog() {
        ThreadLog threadLog = new ThreadLog();
        threadLog.setThreadName(Thread.currentThread().getName());
        threadLog.setThreadPoolName(threadPoolName);
        threadLogService.save(threadLog);
    }

}


interface TestRunnable<String> extends Callable<String> {

}

线程池的执行过程有以下几个要点:
(1)要用线程池执行任务,需要指定线程池的名称,如果线程池容器里没有指定名称的线程池,创建一个新的,如果存在,就直接拿来使用。
(2)需要添加一个定时任务,每隔10s左右(时间自定义),就把线程池容器里的线程池实时运行参数配置,更新到数据库。
(3)不能直接从ThreadPoolExecutor获取阻塞队列的长度,可以通过另外2个参数得到:阻塞队列长度 = 队列元素数量 + 队列剩余容量。

import com.baomidou.mybatisplus.extension.service.IService;
import gdut.entity.DynamicRunnableParams;
import gdut.entity.DynamicThreadPoolParams;
import gdut.entity.ThreadPoolConfig;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.concurrent.ThreadPoolExecutor;

public interface ThreadPoolConfigService extends IService<ThreadPoolConfig> {

    ThreadPoolExecutor addThreadPoolExecutor(String threadPoolName);

    ThreadPoolExecutor updateThreadPoolExecutor(DynamicThreadPoolParams dynamicThreadPoolParams);

    void refreshThreadPoolExecutor();

    void executeRunnable(DynamicRunnableParams dynamicRunnableParams);
}

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import gdut.entity.DynamicRunnableParams;
import gdut.entity.DynamicThreadPoolParams;
import gdut.entity.ThreadPoolConfig;
import gdut.entity.UserLog;
import gdut.mapper.ThreadPoolConfigMapper;
import gdut.mapper.UserLogMapper;
import gdut.service.ArticleService;
import gdut.service.ThreadLogService;
import gdut.service.ThreadPoolConfigService;
import gdut.service.UserLogService;
import gdut.util.DynamicThreadPoolExecutor;
import gdut.util.TestRunnableImpl;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

@Service
@Slf4j
public class ThreadPoolConfigServiceImpl extends ServiceImpl<ThreadPoolConfigMapper, ThreadPoolConfig> implements ThreadPoolConfigService {

    @Autowired
    private ArticleService articleService;

    @Autowired
    private UserLogService userLogService;

    @Autowired
    private ThreadLogService threadLogService;

    public ThreadPoolExecutor addThreadPoolExecutor(String threadPoolName) {
        ThreadPoolExecutor threadPoolExecutor = DynamicThreadPoolExecutor.defaultThreadPoolExecutor(threadPoolName);

        ThreadPoolConfig threadPoolConfig = new ThreadPoolConfig();
        threadPoolConfig.setThreadPoolName(threadPoolName);
        threadPoolConfig.setCoreThreadSize(DynamicThreadPoolExecutor.DEFAULT_CORE_POOL_SIZE);
        threadPoolConfig.setMaximumThreadSize(DynamicThreadPoolExecutor.DEFAULT_MAXIMUM_POOL_SIZE);
        threadPoolConfig.setQueueCapacity(DynamicThreadPoolExecutor.QUEUE_CAPACITY);
        threadPoolConfig.setPoolSize(threadPoolExecutor.getPoolSize());
        threadPoolConfig.setActiveThreads(threadPoolExecutor.getActiveCount());
        threadPoolConfig.setQueueTasks(threadPoolExecutor.getQueue().size());
        threadPoolConfig.setCompletedTasks(threadPoolExecutor.getCompletedTaskCount());
        this.save(threadPoolConfig);

        return threadPoolExecutor;
    }

    public ThreadPoolExecutor updateThreadPoolExecutor(DynamicThreadPoolParams dynamicThreadPoolParams) {
        ThreadPoolExecutor threadPoolExecutor = DynamicThreadPoolExecutor.dynamicThreadPoolExecutor(dynamicThreadPoolParams);

        LambdaUpdateWrapper<ThreadPoolConfig> updateWrapper = new UpdateWrapper<ThreadPoolConfig>().lambda()
                .eq(ThreadPoolConfig::getThreadPoolName, dynamicThreadPoolParams.getThreadPoolName())
                .set(ThreadPoolConfig::getCoreThreadSize, dynamicThreadPoolParams.getCorePoolSize())
                .set(ThreadPoolConfig::getMaximumThreadSize, dynamicThreadPoolParams.getMaximumPoolSize())
                .set(ThreadPoolConfig::getPoolSize, threadPoolExecutor.getPoolSize())
                .set(ThreadPoolConfig::getActiveThreads, threadPoolExecutor.getActiveCount())
                .set(ThreadPoolConfig::getQueueTasks, threadPoolExecutor.getQueue().size())
                .set(ThreadPoolConfig::getCompletedTasks, threadPoolExecutor.getCompletedTaskCount());
        this.update(updateWrapper);

        return threadPoolExecutor;
    }

    public void refreshThreadPoolExecutor() {
        Map<String, ThreadPoolExecutor> executorPoolMap = DynamicThreadPoolExecutor.getExecutorPool();
        if(null == executorPoolMap || executorPoolMap.size() == 0) {
            return;
        }

        for(Map.Entry<String, ThreadPoolExecutor> entry : executorPoolMap.entrySet()) {
            String threaPoolName = entry.getKey();
            ThreadPoolExecutor threadPoolExecutor = entry.getValue();

            LambdaQueryWrapper<ThreadPoolConfig> queryWrapper = new QueryWrapper<ThreadPoolConfig>().lambda()
                    .eq(ThreadPoolConfig::getThreadPoolName, threaPoolName);

            ThreadPoolConfig newPoolConfig = new ThreadPoolConfig();
            newPoolConfig.setThreadPoolName(threaPoolName);
            newPoolConfig.setCoreThreadSize(threadPoolExecutor.getCorePoolSize());
            newPoolConfig.setMaximumThreadSize(threadPoolExecutor.getMaximumPoolSize());
            newPoolConfig.setQueueCapacity(threadPoolExecutor.getQueue().size() + threadPoolExecutor.getQueue().remainingCapacity());
            newPoolConfig.setPoolSize(threadPoolExecutor.getPoolSize());
            newPoolConfig.setActiveThreads(threadPoolExecutor.getActiveCount());
            newPoolConfig.setQueueTasks(threadPoolExecutor.getQueue().size());
            newPoolConfig.setCompletedTasks(threadPoolExecutor.getCompletedTaskCount());

            ThreadPoolConfig threadPoolConfig = this.getOne(queryWrapper);
            if(null == threadPoolConfig) {
                this.save(newPoolConfig);
            } else {
                this.update(newPoolConfig, new UpdateWrapper<ThreadPoolConfig>().lambda()
                    .eq(ThreadPoolConfig::getThreadPoolName, threaPoolName));
            }
        }

        List<String> threadPoolNameList = Lists.newArrayList(executorPoolMap.keySet());
        this.remove(new QueryWrapper<ThreadPoolConfig>().notIn("thread_pool_name", threadPoolNameList));
    }

    @Override
    public void executeRunnable(DynamicRunnableParams dynamicRunnableParams) {
        ThreadPoolExecutor threadPoolExecutor = DynamicThreadPoolExecutor.defaultThreadPoolExecutor(dynamicRunnableParams.getThreadPoolName());

        List<TestRunnableImpl> callableList = Lists.newArrayList();
        for (int i = 0; i < dynamicRunnableParams.getExecuteCount(); i++) {
            TestRunnableImpl testRunnable = new TestRunnableImpl();
            testRunnable.setThreadLogService(threadLogService);
            testRunnable.setArticleService(articleService);
            testRunnable.setUserLogService(userLogService);
            testRunnable.setThreadPoolName(dynamicRunnableParams.getThreadPoolName());

            callableList.add(testRunnable);
        }

        try {
            threadPoolExecutor.invokeAll(callableList);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
import gdut.entity.DynamicRunnableParams;
import gdut.entity.DynamicThreadPoolParams;
import gdut.entity.UpdateThreadParam;
import gdut.service.ArticleService;
import gdut.service.ThreadLogService;
import gdut.service.ThreadPoolConfigService;
import gdut.service.UserLogService;
import gdut.util.DynamicThreadPoolExecutor;
import gdut.util.TestRunnableImpl;
import gdut.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;

@RestController
@RequestMapping("/dynamic")
@Slf4j
public class DynamicThreadPoolController {

    @Autowired
    ThreadPoolConfigService threadPoolConfigService;

    @PostMapping("/executeRunnable")
    public void executeRunnable(@RequestBody DynamicRunnableParams dynamicRunnableParams) {
        threadPoolConfigService.executeRunnable(dynamicRunnableParams);
    }

    @PostMapping("/updateThreadPoolExecutor")
    public String updateThreadPoolExecutor(@RequestBody DynamicThreadPoolParams dynamicThreadPoolParams) {
        threadPoolConfigService.updateThreadPoolExecutor(dynamicThreadPoolParams);
        return "success";
    }

    @PostMapping("/refreshThreadPoolExecutor")
    public String refreshThreadPoolExecutor(@RequestBody DynamicThreadPoolParams dynamicThreadPoolParams) {
        threadPoolConfigService.refreshThreadPoolExecutor();
        return "success";
    }


}

创建线程池执行任务:
http://localhost:8089/dynamic/executeRunnable

{
    "threadPoolName":"Test",
    "executeCount":50
}

拉取线程池配置,写入数据库:
http://localhost:8089/dynamic/refreshThreadPoolExecutor

更新线程池:
http://localhost:8089/dynamic/updateThreadPoolExecutor

{
    "threadPoolName":"Test",
    "corePoolSize":10,
    "maximumPoolSize":30
}

3.参考

Hippo4J官网文档
8个Java线程池最佳实践和坑!
如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。
Java线程池实现原理及其在美团业务中的实践

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/811526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux Day04

目录 一、文件压缩与解压命令 1.1 tar cvf 文件名 ---打包命令生成.tar 1.2 tar xvf 文件名 ----解开包 生成文件 1.3 gzip .tar 压缩 生成.tar.gz压缩包 1.4 gzip -d .tar.gz 解压成包 1.5 直接把压缩包解压成文件 tar zxf .tar.gz 二、Linux 系统上 C 程序的…

Python实现自动登录和下单脚本,代码嘚魅力~

目录标题 前言环境使用:代码实现思路配置浏览器驱动代码实现尾语 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。 Selenium 支持的语言包括C#…

Winget简单介绍

为什么明明Windows的UI更为便捷&#xff0c;但是还是那么多人用的linux&#xff0c;或者mac 主要还是linux和mac的命令行更好用。 虽然windows中也出现了choco这种东西&#xff0c;但是非官方的总觉得不是很令人信服。 这边挂一下官方文档&#xff0c;然后在开始自己的简单实…

深入理解Vue响应式系统:数据绑定探索

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

某某大学某学院后台Phar反序列化GetShell

觉得这个洞还算有点意思&#xff0c;可以记录一下 首先在另一个二级学院进行目录扫描时发现源码www.rar&#xff0c;并且通过一些页面测试推测这两个二级学院应该是使用了同一套CMS 分析源码&#xff0c;发现使用的是ThinkPHP 5.1.34 LTS框架 通过APP、Public得到后台访问路径…

开放自动化软件的硬件平台

自动化行业的产品主要以嵌入式系统为主&#xff0c;历来对产品硬件的可靠性和性能都提出很高的要求。最典型的产品要数PLC。PLC 要求满足体积小&#xff0c;实时性&#xff0c;可靠性&#xff0c;可扩展性强&#xff0c;环境要求高等特点。它们通常采用工业级高性能嵌入式SoC 实…

无涯教程-jQuery - Selectable选择函数

选择能力功能可与JqueryUI中的交互一起使用。此功能可在任何DOM元素上启用选择能力功能。用光标绘制一个框以选择项目。按住Ctrl键可进行多个不相邻的选择。 Select able - 语法 $( "#selectable" ).selectable(); Select able - 示例 以下是一个简单的示例&…

反弹shell确认是否是docker容器

反弹shell确认是否是docker容器 方法一 如果根目录中存在.dockerwenv文件&#xff0c;证明是doker容器 ls /.dockerenv方法二 出现以下类似内容&#xff0c;证明是在doker容器内 cat /proc/1/cgroup

回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图)效果一览基本介绍程序设计参考资料效果一览 基本介绍 MATLAB实现GRNN广义回归神经网络多输入单输出回归…

【设计模式——学习笔记】23种设计模式——享元模式Flyweight(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基本介绍原理类图外部状态和内部状态 登场角色 案例实现案例1类图代码实现 案例2类图代码实现 享元模式在JDK源码中的使用总结 案例引入 你的公司主要做一些小型的外包项目&#xff0c;之前给客户A做一个产品展示网站&#xff0c;客户A的朋友感觉效果不错…

uniapp小程序自定义loding,通过状态管理配置全局使用

一、在项目中创建loding组件 在uniapp的components文件夹下创建loding组件&#xff0c;如图&#xff1a; 示例代码&#xff1a; <template><view class"loginLoading"><image src"../../static/loading.gif" class"loading-img&q…

SpringBoot统一异常处理和统一返回格式

上篇博客我们讲解了使用AOP来进行统一的用户登录判断&#xff0c;其实像这种功能统一且使用较多的地方&#xff0c;都可以用AOP来处理&#xff0c;除了统⼀的⽤户登录判断之外&#xff0c;AOP 还可以实现&#xff1a; 统⼀⽇志记录统⼀⽅法执⾏时间统计&#xff08;在性能优化…

8.事件对象

8.1获取事件对象 ●事件对象是什么 也是个对象&#xff0c;这个对象里有事件触发时的相关信息 例如&#xff1a;鼠标点击事件中&#xff0c;事件对象就存了鼠标点在哪个位置等信息 ●使用场景 可以判断用户按下哪个键&#xff0c;比如按下回车键可以发布新闻 可以判断鼠标点击…

【动态规划】子数组系列

文章目录 动态规划&#xff08;子数组系列&#xff09;1. 最大子数组和2. 环形子数组的最大和3. 乘积最大子数组4. 乘积为正的最长子数组的长度5. 等差数列划分6. 最长湍流子数组7. 单词拆分8. 环形字符串中的唯一的子字符串 动态规划&#xff08;子数组系列&#xff09; 1. 最…

ETHERCAT转CCLINK网关连接ethercat转换器

你们有没有遇到这样的问题&#xff1f;在生产管理系统中&#xff0c;数据互联互通是非常重要的&#xff0c;但ETHERCAT和CCLINK这两个协议之间的通讯一直是个大问题。今天&#xff0c;我给大家带来了一个好消息——捷米JM-ECT-CCLK&#xff0c;这是一款让各种CCLINK总线和ETHER…

WEB:php_rce

背景知识 Linux命令 thinkPHPv5漏洞 题目 打开页面&#xff0c;页面显示为thinkphp v5的界面&#xff0c;可以判断框架为thinkPHP&#xff0c;可以去网上查找相关的漏洞 由题目可知&#xff0c;php rec是一个通过远程代码执行漏洞来攻击php程序的一种方式 因为不知道是php版…

三数之和——力扣15

文章目录 题目描述法一 双指针排序 题目描述 法一 双指针排序 class Solution{ public:vector<vector<int>> threeSum(vector<int>& nums){int nnums.size();vector<vector<int>> ans;sort(nums.begin(), nums.end());for(int first0;first&…

项目实战 — 消息队列(2){创建核心类}

目录 一、创建项目 二、创建核心类 &#x1f345; 1、 编写交换机类&#xff0c;Exchange &#x1f345; 2、编写存储消息的队列&#xff0c;MSGQueue &#x1f345; 3、编写绑定类&#xff0c;binding &#x1f345; 4、编写消息&#xff0c;Message 一、创建项目 二、创…

【Golang 接口自动化04】 解析接口返回JSON串

目录 前言 解析到结构体 json数据与struct字段是如何相匹配的呢&#xff1f; 解析到interface Go类型和JSON类型 实例代码 simpleJson 总结 资料获取方法 前言 上一次我们一起学习了如何解析接口返回的XML数据&#xff0c;这一次我们一起来学习JSON的解析方法。 JSO…

Mysql 索引失效

1、模糊查询%在前面&#xff0c;无法排序所以失效 2、函数计算 3、表达式计算length(NAME) 4、隐式转换 5、联合索引非最左匹配 6、or 必须都为索引列 事务特性 &#xff08;来自小林coding 事务隔离级别是怎么实现的&#xff1f; | 小林coding (xiaolincoding.com)&#…