程序员@老李 - 技术 技术人生 - technical-life 2024-08-29T10:01:00+08:00 Typecho https://blog.mostion.com/feed/atom/category/tech/ <![CDATA[SpringBoot 整合线程池]]> https://blog.mostion.com/archives/53/ 2024-08-29T10:01:00+08:00 2024-08-29T10:01:00+08:00 Jason http://blog.mostion.com 步骤如下:

1. 启动类加入 @EnableAsync 注解

@SpringBootApplication
@EnableAsync
public class FacadeH5Application {
    public static void main(String[] args) {
        SpringApplication.run(FacadeH5Application.class, args);
    }
}

2. 在方法上加 @Async 注解

@Async
public void m1() {
    //do something
}

3. 创建线程池配置文件

# 核心线程数
spring.task.execution.pool.core-size=8  
# 最大线程数
spring.task.execution.pool.max-size=16
# 空闲线程存活时间
spring.task.execution.pool.keep-alive=60s
# 是否允许核心线程超时
spring.task.execution.pool.allow-core-thread-timeout=true
# 线程队列数量
spring.task.execution.pool.queue-capacity=100
# 线程关闭等待
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-
@Configuration
public class ThreadPoolConfig {
    @Bean
    public TaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //设置队列容量
        executor.setQueueCapacity(20);
        //设置线程活跃时间
        executor.setKeepAliveSeconds(30);
        //设置线程名称前缀
        executor.setThreadNamePrefix("sendSms-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池中任务的等待时间
        executor.setAwaitTerminationSeconds(60);

        return executor;
    }
}

配置多个线程池

@Configuration
public class ThreadPoolConfig {
    @Bean("ThreadPool1")
    public TaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        ......
        return executor;
    }
    @Bean("ThreadPool2")
    public TaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        ......
        return executor;
    }
}

在使用 @Async 注解时就需要指定具体的线程池

@Async("ThreadPool1")
public void m1() {
    //do something
}
]]>
<![CDATA[SpringBoot静态方法调用Spring容器bean的几种方案]]> https://blog.mostion.com/archives/49/ 2023-12-14T13:47:13+08:00 2023-12-14T13:47:13+08:00 Jason http://blog.mostion.com 今天遇到一个问题,需要在静态方法中调用哦那个容器Bean,大致代码如下:

@Autowired
private static RedisUtil redisUtilBean;

public static String getMsgByRedis(){
    redisUtilBean.get("xxx")  //这里redisUtilBean一定会是NULL值
}

为什么会出现这种情况?原因是Spring容器的依赖注入是依赖set方法,而set方法是实例对象的方法,注入依赖时是无法注入静态成员变量的,在调用的时候依赖的Bean才会为null;

解决方案一

使用@PostConstruct注解:

@Autowired
private RedisUtil redisUtilBean;
    
//由于静态方法无法使用注入的Bean 定义静态变量
private static RedisUtil redisUtil;

//当容器实例化当前受管Bean时@PostConstruct注解的方法会被自动触发,借此来实现静态变量初始化
@PostConstruct
public void init(){
   this.redisUtil = redisUtilBean;
}
    
public static String getMsgByRedis(){
   redisUtil.get("xxx")  //这里可以正常使用
}

解决方案二

利用springboot的启动类中,SpringApplication.run() 方法返回的是一个ConfigurableApplicationContext对象通过定义static变量ConfigurableApplicationContext,利用容器的getBean方法获得依赖对象;

@SpringBootApplication
@EnableTransactionManagement
public class Application {
    //定义静态的ApplicationContext
    public static ConfigurableApplicationContext applicationContext;
    public static void main(String[] args) {
        applicationContext = SpringApplication.run(Application.class, args);
    }
}

//调用  注意Application是我们SpringBoot的启动类
public static String getMsgByRedis(){
    Application.applicationContext.getBean(RedisUtil .class).get("xxx") 
}

解决方案三

在我们以前SpringMVC中常用的工具类,通过实现ApplicationContextAware接口,网上也很多这里就把工具类贴出来即可;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.toher.common.utils.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext.
 *
 * @author 李怀明
 * @version 2017-01-02
 */
@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    //实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextHolder.applicationContext = applicationContext;
    }

    //取得存储在静态变量中的ApplicationContext.
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    //从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        checkApplicationContext();
        return (T) applicationContext.getBean(name);
    }

    //从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
    //如果有多个Bean符合Class, 取出第一个.
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        @SuppressWarnings("rawtypes")
        Map beanMaps = applicationContext.getBeansOfType(clazz);
        if (beanMaps != null && !beanMaps.isEmpty()) {
            return (T) beanMaps.values().iterator().next();
        } else {
            return null;
        }
    }

    private static void checkApplicationContext() {
        if (applicationContext == null) {
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
        }
    }

    public static HttpServletRequest getRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            return null;
        }
    }

}

调用方法:

RedisUtil redisUtil= (RedisUtil) SpringContextHolder.getBean(RedisUtil.class);

结束,感谢!

]]>
<![CDATA[SpringBoot 自定义线程池]]> https://blog.mostion.com/archives/48/ 2023-11-29T08:51:00+08:00 2023-11-29T08:51:00+08:00 Jason http://blog.mostion.com 我们都知道spring只是为我们简单的处理线程池,每次用到线程总会new 一个新的线程,效率不高,所以我们需要自定义一个线程池。

本教程目录:

  1. 自定义线程池
  2. 配置spring默认的线程池

1. 自定义线程池

1.1 修改application.properties

task.pool.corePoolSize=20
task.pool.maxPoolSize=40
task.pool.keepAliveSeconds=300
task.pool.queueCapacity=50

1.2 线程池配置属性类TaskThreadPoolConfig.java

import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * 线程池配置属性类
 * Created by Fant.J.
 */
@ConfigurationProperties(prefix = "task.pool")
public class TaskThreadPoolConfig {
    private int corePoolSize;

    private int maxPoolSize;

    private int keepAliveSeconds;

    private int queueCapacity;
    ...getter and setter methods...
}

1.3 创建线程池 TaskExecutePool.java

/**
 * 创建线程池
 * Created by Fant.J.
 */
@Configuration
@EnableAsync
public class TaskExecutePool {
    @Autowired
    private TaskThreadPoolConfig config;

    @Bean
    public Executor myTaskAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池大小
        executor.setCorePoolSize(config.getCorePoolSize());
        //最大线程数
        executor.setMaxPoolSize(config.getMaxPoolSize());
        //队列容量
        executor.setQueueCapacity(config.getQueueCapacity());
        //活跃时间
        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
        //线程名字前缀
        executor.setThreadNamePrefix("MyExecutor-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

1.4 创建线程任务

/**
* Created by Fant.J.
*/
@Component
public class AsyncTask {
   protected final Logger logger = LoggerFactory.getLogger(this.getClass());

   @Async("myTaskAsyncPool")  //myTaskAsynPool即配置线程池的方法名,此处如果不写自定义线程池的方法名,会使用默认的线程池
   public void doTask1(int i) throws InterruptedException{
       logger.info("Task"+i+" started.");
   }
}

1.5 修改启动类

给启动类添加注解
@EnableAsync
@EnableConfigurationProperties({TaskThreadPoolConfig.class} ) // 开启配置属性支持

1.6 测试

protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void AsyncTaskTest() throws InterruptedException, ExecutionException {

        for (int i = 0; i < 100; i++) {
            asyncTask.doTask1(i);
        }
        logger.info("All tasks finished.");
    }

2. 配置默认的线程池

我本人喜欢用这种方式的线程池,因为上面的那个线程池使用时候总要加注解@Async("myTaskAsyncPool"),而这种重写spring默认线程池的方式使用的时候,只需要加@Async注解就可以,不用去声明线程池类。

2.1 获取属性配置类

这个和上面的TaskThreadPoolConfig类相同,这里不重复

2.2 NativeAsyncTaskExecutePool.java 装配线程池

/**
 * 原生(Spring)异步任务线程池装配类
 * Created by Fant.J.
 */
@Slf4j
@Configuration
public class NativeAsyncTaskExecutePool implements AsyncConfigurer{


    //注入配置类
    @Autowired
    TaskThreadPoolConfig config;

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池大小
        executor.setCorePoolSize(config.getCorePoolSize());
        //最大线程数
        executor.setMaxPoolSize(config.getMaxPoolSize());
        //队列容量
        executor.setQueueCapacity(config.getQueueCapacity());
        //活跃时间
        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
        //线程名字前缀
        executor.setThreadNamePrefix("MyExecutor-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }


    /**
     *  异步任务中异常处理
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {

            @Override
            public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {
                log.error("=========================="+arg0.getMessage()+"=======================", arg0);
                log.error("exception method:"+arg1.getName());
            }
        };
    }
}

2.3 线程任务类AsyncTask .java

@Component
public class AsyncTask {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Async
    public void doTask2(int i) throws InterruptedException{
        logger.info("Task2-Native"+i+" started.");
    }
}

2.4 测试

@Test
public void AsyncTaskNativeTest() throws InterruptedException, ExecutionException {
    for (int i = 0; i < 100; i++) {
        asyncTask.doTask2(i);
    }
    logger.info("All tasks finished.");
}

2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-8] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native6 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-3] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native1 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native7 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native21 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native22 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native23 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native24 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native25 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native26 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native27 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native28 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native29 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native30 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native31 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native32 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native33 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native34 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native35 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native36 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native37 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native38 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native39 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native40 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native41 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native42 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native43 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native44 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native45 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native46 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native47 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-7] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native5 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-7] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native49 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-7] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native50 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-11] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native9 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native4 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native53 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native54 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native55 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native56 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native57 started.
2018-03-25 21:23:07.659 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native58 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native59 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native60 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native61 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native62 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native63 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native64 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native65 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native66 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native67 started.
2018-03-25 21:23:07.660 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native68 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-5] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native3 started.
2018-03-25 21:23:07.655 INFO 4668 --- [ MyExecutor-4] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native2 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-8] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native19 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-2] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native0 started.
2018-03-25 21:23:07.656 INFO 4668 --- [ MyExecutor-3] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native20 started.
2018-03-25 21:23:07.657 INFO 4668 --- [ MyExecutor-10] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native8 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native48 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-7] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native51 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-11] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native52 started.
2018-03-25 21:23:07.658 INFO 4668 --- [ MyExecutor-12] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native10 started.
2018-03-25 21:23:07.661 INFO 4668 --- [ MyExecutor-13] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native11 started.
2018-03-25 21:23:07.662 INFO 4668 --- [ MyExecutor-14] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native12 started.
2018-03-25 21:23:07.662 INFO 4668 --- [ MyExecutor-15] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native13 started.
2018-03-25 21:23:07.663 INFO 4668 --- [ MyExecutor-16] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native14 started.
2018-03-25 21:23:07.663 INFO 4668 --- [ MyExecutor-17] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native15 started.
2018-03-25 21:23:07.663 INFO 4668 --- [ MyExecutor-18] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native16 started.
2018-03-25 21:23:07.663 INFO 4668 --- [ MyExecutor-19] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native17 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-20] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native18 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-21] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native69 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ main] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native89 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-6] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native90 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-22] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native70 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-5] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native91 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-5] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native92 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-8] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native93 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-2] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native94 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-10] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native95 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-3] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native96 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-7] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native98 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-9] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native97 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ MyExecutor-11] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native99 started.
2018-03-25 21:23:07.664 INFO 4668 --- [ main] com.laojiao.securitydemo.ControllerTest : All tasks finished.
2018-03-25 21:23:07.666 INFO 4668 --- [ MyExecutor-23] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native71 started.
2018-03-25 21:23:07.667 INFO 4668 --- [ MyExecutor-24] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native72 started.
2018-03-25 21:23:07.667 INFO 4668 --- [ MyExecutor-25] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native73 started.
2018-03-25 21:23:07.669 INFO 4668 --- [ MyExecutor-26] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native74 started.
2018-03-25 21:23:07.669 INFO 4668 --- [ MyExecutor-27] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native75 started.
2018-03-25 21:23:07.673 INFO 4668 --- [ MyExecutor-28] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native76 started.
2018-03-25 21:23:07.674 INFO 4668 --- [ MyExecutor-29] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native77 started.
2018-03-25 21:23:07.674 INFO 4668 --- [ MyExecutor-30] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native78 started.
2018-03-25 21:23:07.676 INFO 4668 --- [ MyExecutor-31] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native79 started.
2018-03-25 21:23:07.677 INFO 4668 --- [ MyExecutor-32] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native80 started.
2018-03-25 21:23:07.677 INFO 4668 --- [ MyExecutor-33] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native81 started.
2018-03-25 21:23:07.677 INFO 4668 --- [ MyExecutor-34] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native82 started.
2018-03-25 21:23:07.678 INFO 4668 --- [ MyExecutor-35] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native83 started.
2018-03-25 21:23:07.679 INFO 4668 --- [ MyExecutor-36] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native84 started.
2018-03-25 21:23:07.679 INFO 4668 --- [ MyExecutor-37] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native85 started.
2018-03-25 21:23:07.679 INFO 4668 --- [ MyExecutor-38] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native86 started.
2018-03-25 21:23:07.680 INFO 4668 --- [ MyExecutor-39] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native87 started.
2018-03-25 21:23:07.680 INFO 4668 --- [ MyExecutor-40] c.l.securitydemo.mythreadpool.AsyncTask : Task2-Native88 started.

作者:PlayInJava
链接:https://juejin.cn/post/6844903584857849870
]]>
<![CDATA[Git 拉取远程分支代码并合并到本地分支]]> https://blog.mostion.com/archives/46/ 2023-11-16T16:20:00+08:00 2023-11-16T16:20:00+08:00 Jason http://blog.mostion.com 1. 使用git fetch命令

需要在本地额外新建分支的 :

// 查看远程
git remote -v 
// 获取远程指定分支到本地临时新建的分支
// 获取远程master的分支的代码到临时新建的temp
git fetch origin master:temp
// 查看版本差异
// 查看temp分支与当前分支的差异
git diff temp 
// 将临时分支temp合并到当前分支
git merge tmep
// 删除临时分支
git branch -D temp

不要在本地新建分支的 :

// 查看远程
git remote -v
// 获取远程分支到本地
// 获取远程的master分支
git fetch origin master 
// 查看版本差异
// 查看远程master分支与本地master分支的差别
git log -p master..origin/master
// 合并到本地分支
git merge origin/master

2.使用git pull命令

相当于git fetch与git merge一起使用,但是这样使用容易出错所以推荐第一种方式

// 查看远程
git remote -v
// 拉取并合并到本地分支
// 拉取远程的master分支合并到当前分支
git pull origin master
]]>
<![CDATA[CentOS yum 安装lnmp相关配置]]> https://blog.mostion.com/archives/36/ 2023-11-09T13:18:21+08:00 2023-11-09T13:18:21+08:00 Jason http://blog.mostion.com 查看系统版本
cat /etc/os-release
cat /etc/redhat-release
rpm -q centos-release

查看内核

uname -a

安装epel

yum -y install epel-release

rpm -ivh http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

安装remi

#remi源需要手动启用
rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm --import http://rpms.famillecollet.com/RPM-GPG-KEY-remi
yum install -y yum-utils
yum-config-manager --enable remi > /dev/null

安装php71

#!/bin/bash
yum -y install \
openssl \
openssl-devel \
libmcrypt \
libmcrypt-devel \
php71 \
php71-php \
php71-php-gd \
php71-php-mysqlnd \
php71-php-mcrypt \
php71-php-imap \
php71-php-ldap \
php71-php-mbstring \
php71-php-pear \
php71-php-xml \
php71-php-xmlrpc \
php71-php-soap \
php71-php-bcmath \
php71-php-mcrypt \
php71-php-fpm \
php71-php-pecl-http \
php71-php-pecl-redis \
php71-php-opcache \
php71-php-pecl-zip //phpexcel 必须
#!/bin/bash
yum -y install \
openssl \
openssl-devel \
libmcrypt \
libmcrypt-devel \
php74 \
php74-php \
php74-php-gd \
php74-php-mysqlnd \
php74-php-mcrypt \
php74-php-imap \
php74-php-ldap \
php74-php-mbstring \
php74-php-pear \
php74-php-xml \
php74-php-xmlrpc \
php74-php-soap \
php74-php-bcmath \
php74-php-mcrypt \
php74-php-fpm \
php74-php-pecl-http \
php74-php-pecl-redis \
php74-php-opcache \
php74-php-pecl-zip //phpexcel 必须
yum -y install openssl openssl-devel libmcrypt libmcrypt-devel php71 php71-php php71-php-gd php71-php-mysqlnd php71-php-mcrypt php71-php-imap php71-php-ldap php71-php-mbstring php71-php-pear php71-php-xml php71-php-xmlrpc php71-php-soap php71-php-bcmath php71-php-mcrypt php71-php-fpm php71-php-pecl-http php71-php-pecl-redis php71-php-opcache php71-php-pecl-zip
yum -y install openssl openssl-devel libmcrypt libmcrypt-devel php72 php72-php php72-php-gd php72-php-mysqlnd php72-php-mcrypt php72-php-imap php72-php-ldap php72-php-mbstring php72-php-pear php72-php-xml php72-php-xmlrpc php72-php-soap php72-php-bcmath php72-php-mcrypt php72-php-fpm php72-php-pecl-http php72-php-pecl-redis php72-php-opcache php72-php-pecl-zip
yum -y install openssl openssl-devel libmcrypt libmcrypt-devel php73 php73-php php73-php-gd php73-php-mysqlnd php73-php-mcrypt php73-php-imap php73-php-ldap php73-php-mbstring php73-php-pear php73-php-xml php73-php-xmlrpc php73-php-soap php73-php-bcmath php73-php-mcrypt php73-php-fpm php73-php-pecl-http php73-php-pecl-redis php73-php-opcache php73-php-pecl-zip php73-php-pecl-redis4 php73-php-phalcon4
yum -y install openssl openssl-devel libmcrypt libmcrypt-devel php80 php80-php php80-php-gd php80-php-mysqlnd php80-php-mcrypt php80-php-imap php80-php-ldap php80-php-mbstring php80-php-pear php80-php-xml php80-php-xmlrpc php80-php-soap php80-php-bcmath php80-php-mcrypt php80-php-fpm php80-php-pecl-http  php80-php-opcache php80-php-pecl-zip php80-php-pecl-redis5 php80-php-phalcon4
#修改yum设置,让rpm包缓存到本地
vi /etc/yum.conf
#修改keepcache为1
keepcache=1
#清空yum缓存
yum clean all
#安装你要离线安装的rpm包
yum install xxx.rpm -y 
#rpm包缓存到了/var/cache/yum下面
cd /var/cache/yum
find ./ -name *.rpm
#找到你的rpm包,并拷走就可以离线安装了

安装php56

#!/bin/bash
yum -y install \
libmcrypt \
libmcrypt-devel \
# libjpeg* \
php56 \
php56-php \
php56-php-gd \
php56-php-mysqlnd \
php56-php-mcrypt \
php56-php-imap \
php56-php-ldap \
php56-php-mbstring \
php56-php-pear \
php56-php-xml \
php56-php-xmlrpc \
php56-php-soap \
php56-php-bcmath \
php56-php-mcrypt \
php56-php-fpm \
php56-php-pecl-http \
php56-php-pecl-redis \
php56-php-opcache
# 查看某个包的信息
yum --enablerepo=epel info htop

# 列出epel信息
yum --disablerepo="*" --enablerepo="epel" list available | less

安装MySQL

访问地址 下载对应的rpm包

wget https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
yum localinstall mysql80-community-release-el7-1.noarch.rpm

手动启用想要的版本

yum -y install mysql-community-server
yum -y install mysql-community-client

启动服务

# 启动服务
systemctl start mysqld
# 初始化
mysql_secure_installation

初始密码

cat /var/log/mysqld.log

MySQL 2059错误

alter user root@localhost identified by 'password' password expire never;
alter user root@localhost identified with mysql_native_password by 'password';
flush privileges;

安装开发工具包

yum groupinstall "Development Tools" "Development Libraries"
yum install gcc gcc-c++ gcc-g77 flex bison \
autoconf automake bzip2-devel zlib-devel \
ncurses-devel libjpeg-devel libpng-devel \
libtiff-devel freetype-devel pam-devel \
openssl-devel libxml2-devel gettext-devel \
pcre-devel

系统服务目录

/usr/lib/systemd/system/    #RPM包安装时分发的unit文件
/run/systemd/system/        #systemd运行时创建的文件
/etc/systemd/system/        #systemctl enable创建的unit文件

Linux Headers

  • redhat系列

    sudo yum -y install kernel-headers  //安装kernel-headers
    sudo yum -y install kernel-devel    //安装kernel-devel
  • debian系列

    sudo apt-get install build-essential  //install build-essential(optional)
    sudo apt-get update                  //install linux-headers
    sudo apt-get install linux-headers-$(uname -r)
    sudo apt-get update && sudo apt-get install build-essential linux-headers-$(uname -r)
#discuz 安全

location ~ /(template|attachment|upload)/.*\.(php|php5|PHP|PHP5)?$ {
      deny all;
}
]]>
<![CDATA[Laravel实现适用于API的分页查询]]> https://blog.mostion.com/archives/34/ 2023-11-06T08:36:00+08:00 2023-11-06T08:36:00+08:00 Jason http://blog.mostion.com Laravel 实现适用于 API 的分页查询

Laravel自带分页查询方法里有一些多余的数据,并不完美适用于我们用来做API的查询。本文旨在通过重写 paginate 解决分页查询针对API接口的的灵活性和适用性。

新建BaseModel.php文件,代码如下:

<?php

namespace App\Http\Model;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\Paginator;

class BaseModel extends Model{
    public $timestamps = false;
    //重写分页方法,使其更加匹配API的规范
    //考虑到性能问题,通常$columns我们不以*号为值。可传入需要查询的字段替代。这里只做演示。无此要求
    public function paginate($perPage = null, $columns = ['*'], $page = null, $pageName = 'page'){
        $page = $page ?: Paginator::resolveCurrentPage($pageName);

        $perPage = $perPage ?: $this->model->getPerPage();

        $results = ($total = $this->toBase()->getCountForPagination())
            ? $this->forPage($page, $perPage)->get($columns)
            : $this->model->newCollection();
        $pages = ceil($total / $perPage);
        $result = ['total'=>$total,'current_page'=>$page,'page_size'=>$perPage,'pages'=>$pages,'list'=>$results];
        return $result;
    }
}

然后在业务模型继承此类即可,范例如下:

<?php

namespace App\Http\Model;

class MsgBoard extends BaseModel{
    protected $table = 'msg_board';

    public function getMsgPageList($page,$pageSize){
        return $this->paginate($pageSize, ['*'], $page, 'page');
    }
}

然后在控制器当中调用即可。范例如下:

$page = $request->input('page',1);
$pageSize = $request->input('pageSize',12);
$msgList = $mMsgBoard->getMsgPageList($page,$pageSize);

THE END;

]]>
<![CDATA[php-fpm 调优和配置]]> https://blog.mostion.com/archives/33/ 2023-11-05T09:08:20+08:00 2023-11-05T09:08:20+08:00 Jason http://blog.mostion.com 知识点
  • 一般 php-fpm 进程占用20~30m左右的内存就按30m算。如果单独跑php-fpm,动态方式起始值可设置物理内存Mem/30M,由于大家一般Nginx、MySQL都在一台机器上,于是预留一半给它们,即php-fpm进程数(max_children)为$Mem/2/30。
  • max_requests:指一个php-fpm的工作进程在处理多少个请求后就终止掉。这个用来处理因为PHP解析器或引用的第三方库时,造成的内存泄露问题。
  • 一般原则是:动态适合小内存机器,灵活分配进程,省内存。静态适用于大内存机器,动态创建回收进程对服务器资源也是一种消耗

    常用命令

  • 查看当前php-fpm总进程数,命令:ps -ylC php-fpm --sort:rss 。其中RSS就是占用的内存情况
  • 查看当前php-fpm进程的内存占用情况及启动时间,命令如下:

    ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid'|grep www|sort -nrk5
  • 查看当前php-fpm进程平均占用内存情况:

    ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"M") }'

    配置详解:

    pid = /usr/local/var/run/php-fpm.pid
    #pid设置,一定要开启,上面是Mac平台的。默认在php安装目录中的var/run/php-fpm.pid。比如centos的在: /usr/local/php/var/run/php-fpm.pid
    error_log = /usr/local/var/log/php-fpm.log
    #错误日志,上面是Mac平台的,默认在php安装目录中的var/log/php-fpm.log,比如centos的在: /usr/local/php/var/log/php-fpm.log
    log_level = notice
    #错误级别. 上面的php-fpm.log纪录的登记。可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.
    emergency_restart_threshold = 60
    emergency_restart_interval = 60s
    #表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。0 表示 '关闭该功能'. 默认值: 0 (关闭).
    process_control_timeout = 0
    #设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0.
    daemonize = yes
    #后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。
    listen = 127.0.0.1:9000
    #fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置。如果nginx和php在不同的机器上,分布式处理,就设置ip这里就可以了。
    listen.backlog = -1
    #backlog数,设置 listen 的半连接队列长度,-1表示无限制,由操作系统决定,此行注释掉就行。backlog含义参考:http://www.3gyou.cc/?p=41
    listen.allowed_clients = 127.0.0.1
    #允许访问FastCGI进程的IP白名单,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接。
    listen.owner = www
    listen.group = www
    listen.mode = 0666
    #unix socket设置选项,如果使用tcp方式访问,这里注释即可。
    user = www
    group = www
    #启动进程的用户和用户组,FPM 进程运行的Unix用户, 必须要设置。用户组,如果没有设置,则默认用户的组被使用。
    pm = dynamic 
    #php-fpm进程启动模式,pm可以设置为static和dynamic和ondemand
    #如果选择static,则进程数就数固定的,由pm.max_children指定固定的子进程数。
    #如果选择dynamic,则进程数是动态变化的,由以下参数决定:
    pm.max_children = 50 #子进程最大数
    pm.start_servers = 2 #启动时的进程数,默认值为: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
    pm.min_spare_servers = 1 #保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
    pm.max_spare_servers = 3 #,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理
    pm.max_requests = 500
    #设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 '0' 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.
    pm.status_path = /status
    #FPM状态页面的网址. 如果没有设置, 则无法访问状态页面. 默认值: none. munin监控会使用到
    ping.path = /ping
    #FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。
    ping.response = pong
    #用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong.
    access.log = log/$pool.access.log
    #每一个请求的访问日志,默认是关闭的。
    access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
    #设定访问日志的格式。
    slowlog = log/$pool.log.slow
    #慢请求的记录日志,配合request_slowlog_timeout使用,默认关闭
    request_slowlog_timeout = 10s
    #当一个请求该设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 '0' 表示 'Off'
    request_terminate_timeout = 0
    #设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的'max_execution_time'因为某些特殊原因没有中止运行的脚本有用. 设置为 '0' 表示 'Off'.当经常出现502错误时可以尝试更改此选项。
    rlimit_files = 1024
    #设置文件打开描述符的rlimit限制. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。
    rlimit_core = 0
    #设置核心rlimit最大限制值. 可用值: 'unlimited' 、0或者正整数. 默认值: 系统定义值.
    chroot =
    #启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.
    chdir =
    #设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)
    catch_workers_output = yes
    #重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.
]]>
<![CDATA[Spring Boot中如何优雅地实现异步调用]]> https://blog.mostion.com/archives/30/ 2023-11-03T18:25:00+08:00 2023-11-03T18:25:00+08:00 Jason http://blog.mostion.com 前言

SpringBoot想必大家都用过,但是大家平时使用发布的接口大都是同步的,那么你知道如何优雅的实现异步呢?
这篇文章就是关于如何在Spring Boot中实现异步行为的。但首先,让我们看看同步和异步之间的区别。

  • 同步编程:在同步编程中,任务一次执行一个,只有当一个任务完成时,下一个任务才会被解除阻塞。
  • 异步编程:在异步编程中,可以同时执行多个任务。您可以在上一个任务完成之前转到另一个任务。

WX20231103-181655@2x.png

Spring Boot中,我们可以使用@Async注解来实现异步行为。

实现步骤

  1. 定义一个异步服务接口 AsyncService.java
public interface AsyncService {
    void asyncMethod() throws InterruptedException;
    Future<String> futureMethod() throws InterruptedException;
}
  1. 实现定义的接口 AsyncServiceImpl.java
@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService  {

    @Async
    @Override
    public void asyncMethod() throws InterruptedException {
        Thread.sleep(3000);
        log.info("Thread: [{}], Calling other service..", Thread.currentThread().getName());
    }

    @Async
    @Override
    public Future<String> futureMethod() throws InterruptedException {
        Thread.sleep(5000);
        log.info("Thread: [{}], Calling other service..", Thread.currentThread().getName());
        return new AsyncResult<>("task Done");
    }
}
  • AsyncServiceImpl 是一个 spring 管理的 bean
  • 您的异步方法必须是公共的,而且是被 @Async 注解修饰。
  • 返回类型被限制为 voidFuture
  1. 定义一个控制器AsyncController.java
@EnableAsync
@RestController
@Slf4j
public class AsyncController {
    @Autowired
    AsyncService asyncService;

    @GetMapping("/async")
    public String asyncCallerMethod() throws InterruptedException {
        long start = System.currentTimeMillis();
        log.info("call async method, thread name: [{}]", Thread.currentThread().getName());
        asyncService.asyncMethod();
        String response = "task completes in :" +
                (System.currentTimeMillis() - start) + "milliseconds";
        return response;
    }

    @GetMapping("/asyncFuture")
    public String asyncFuture() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        log.info("call async method, thread name: [{}]", Thread.currentThread().getName());
        Future<String> future = asyncService.futureMethod();
        // 阻塞获取结果
        String taskResult = future.get();
        String response = taskResult + "task completes in :" +
                (System.currentTimeMillis() - start) + "milliseconds";
        return response;
    }
}
  • 关键点,需要添加启用异步的注解@EnableAsync ,当然这个注解加在其他地方也ok得。
  • 当外部调用该接口时,asyncMethod() 将由默认任务执行程序创建的另一个线程执行,主线程不需要等待完成异步方法执行。
  1. 运行一下

现在我们运行一下看看,是不是异步返回的。

WX20231103-182039@2x.png

  • 可以看到调用 /async 接口,最终一步调用了方法。

WX20231103-182050@2x.png

  • 调用 /asyncFuture ,发现返回5秒多,难道不是异步的吗?其实也是异步的,看日志可以看出来,只不过我们返回的是 Future ,调用 Futrue.get() 是阻塞的。

自定义异步任务执行器和异常处理

我们现在看看如果异常方法中报错了会怎么样?修改异步代码如下所示,会抛运行时异常:

WX20231103-182249@2x.png

再次执行异步接口,如下所示,会使用默认的线程池和异常处理。

WX20231103-182320@2x.png

我们也可以自定义异步方法的处理异常和异步任务执行器,我们需要配置 AsyncUncaughtExceptionHandler,如下代码所示:

@Configuration
public class AsynConfiguration extends AsyncConfigurerSupport {
   @Override
   public Executor getAsyncExecutor() {
      ThreadPoolTaskExecutor executor = new 
                ThreadPoolTaskExecutor();
      executor.setCorePoolSize(3);
      executor.setMaxPoolSize(4);
      executor.setThreadNamePrefix("asyn-task-thread-");
      executor.setWaitForTasksToCompleteOnShutdown(true);
      executor.initialize();
      return executor;
  }
  @Override
  public AsyncUncaughtExceptionHandler  
         getAsyncUncaughtExceptionHandler() {
     return new AsyncUncaughtExceptionHandler() {
   
        @Override
        public void handleUncaughtException(Throwable ex, 
           Method method, Object... params) {
           System.out.println("Exception: " + ex.getMessage());
           System.out.println("Method Name: " + method.getName());
           ex.printStackTrace();
        }
    };
  }
}

再次运行,得到的结果如下:

WX20231103-182416@2x.png

@Async如何工作的

必须通过使用 @EnableAsync 注解注解主应用程序类或任何直接或间接异步方法调用程序类来启用异步支持。主要通过代理模式实现,默认模式是 Proxy,另一种是 AspectJ。代理模式只允许通过代理拦截调用。永远不要从定义它的同一个类调用异步方法,它不会起作用。
当使用 @Async 对方法进行注解时,它会根据“proxyTargetClass”属性为该对象创建一个代理。当 spring 执行这个方法时,默认情况下它会搜索关联的线程池定义。上下文中唯一的 spring 框架 TaskExecutor bean 或名为“taskExecutor”的 Executor bean。如果这两者都不可解析,默认会使用spring框架 SimpleAsyncTaskExecutor 来处理异步方法的执行。

总结

在本文中,我们演示了在 spring boot 中如何使用 @Async 注解和异步方法中的异常处理实现异步行为。我们可以在一个接口中,需要访问不同的资源,比如异步调用各个其他服务的接口,可以使用 @Async,然后将结果通过 Future 的方式阻塞汇总,不失为一个提高性能的好方法。

]]>
<![CDATA[制作 macOS Catalina 安装U盘]]> https://blog.mostion.com/archives/21/ 2023-11-03T14:35:00+08:00 2023-11-03T14:35:00+08:00 Jason http://blog.mostion.com 若您已经从 App Store 下载了 macOS Catalina 安装文件,您可以按照以下步骤通过 U 盘进行 macOS 安装:

准备一个容量在 16GB 以上的 U 盘,连接到电脑上。

在应用程序中打开“终端”。

在终端中输入以下命令:

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

其中,/Applications/Install\ macOS\ Catalina.app 是您下载的 macOS Catalina 安装文件的路径,/Volumes/MyVolume 是 U 盘的路径,您可以根据实际情况进行修改。

按回车键,并在提示输入管理员密码时输入您的管理员密码。

在终端完成操作后,您会看到“Install media now available”的提示,这意味着您已经成功地将 macOS Catalina 安装文件写入了 U 盘中。

拔出 U 盘,随后您可以将其插入需要安装 macOS Catalina 的电脑中,重启电脑并按照安装向导完成 macOS Catalina 的安装。

值得注意的是,在使用 U 盘进行 macOS 安装前,您需要先备份您的电脑数据以避免数据丢失。

如果出现如下错误:

APFS disks may not be used as bootable install media. An error occurred erasing the disk.

打开磁盘工具点击左侧外置U盘选择抹掉格式为Mac OS扩展

20191002220357579.png

再次制作安装磁盘

]]>
<![CDATA[SpringBoot外部配置文件的用法]]> https://blog.mostion.com/archives/19/ 2023-11-03T14:19:00+08:00 2023-11-03T14:19:00+08:00 Jason http://blog.mostion.com 1. 加载外部配置文件
spring.config.additional-location=file:/etc/myapp/config/,classpath:/config/
-Dspring.config.additional-location=file:/etc/myapp/config/,classpath:/config/

2. 配置文件在 jar 包的同级目录下的 config 目录下,优先级最高

3. 读取外部 logback 配置文件

java -jar -Dlogging.config=./config/logback-spring.xml datasync-web.jar
]]>