异步 多线程 线程池 异步编排 ThreadLocal的使用

多线程和线程池的使用

/**
* 启动方式+定义线程
* 1)、继承Thread
*      Threade1 thread new Threade1()j
*      thread.start();//启动线程
*
*          *      public static class Thread01 extends Thread {
*          *         @Override
*          *         public void run() {
*          *             System.out.println("当前线程:" + Thread.currentThread().getId());
*          *             int i = 10 / 2;
*          *             System.out.println("运行结果:" + i);
*          *         }
*          *     }
*
* 2)、实现Runnable:接口
*      Runable01 runable01 new Runable01();
*      new Thread(runabLee1).start();
*
*          *     public static class Runable01 implements Runnable {
*          *         @Override
*          *         public void run() {
*          *             System.out.println("当前线程:" + Thread.currentThread().getId());
*          *             int i = 10 / 2;
*          *             System.out.println("运行结果:" + i);
*          *         }
*          *     }
*
* 3)、实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
*      FutureTask<Integer>futureTask new FutureTask<>(new CaLLable01())j
*      new Thread(futureTask).start();
*      //阻塞等待整个线程执行完成,获取返回结果
*      Integer integer =futureTask.get();
*
*          *     public static class Callable01 implements Callable<Integer> {
*          *         @Override
*          *         public Integer call() throws Exception {
*          *             System.out.println("当前线程:" + Thread.currentThread().getId());
*          *             int i = 10 / 2;
*          *             System.out.println("运行结果:" + i);
*          *             return i;
*          *         }
*          *     }
*
* 4)、线程池  我们以后在业务代码里面,以上三种启动线程的方式都不用。【将所有的多线程异步任务都交给线程池执行】
*      定义一个线程池【有很多种线程池】
*          public static ExecutorService service = Executors.newFixedThreadPool(10);
*
*         // 异步任务提交给线程池执行
*         // 没有返回结果 [可以提交 runnable或者callable]
*         service.execute(new Runnable01());
*         // 有返回结果
*         service.submit(new Runnable01());
*
* 区别:
*      1、2不能得到返回值。3可以获取返回值
*      1、2、3都不能控制资源
*      4可以控制资源,性能稳定
*/

线程池

/**
*
* 线程池七大参数
* corePoolSize:[5]核心线程数[一直存在除非设置(aLLowCoreThreadTimeOut)];线程池,创建好以后就准备执行任务
* maximumPoolSize:[200]最大线程数量;控制资源【包括核心线程数,比如最大20个,核心7个,只能再开13个】
* keepAliveTime:存活时间。如果当前的线程数量大于core数量。只要线程空闲大于指定的keepALiveTime;
*          释放空闲的线程数量:(当前的线程数量-corePoolSize)。
* unit:存活时间的单位
* BLockingQueue<Runnable> workQueue:阻塞队列。如果任务有很多,就会将多出的任务放在队列里面。
*      只要有线程空闲,就会去队列里面取出新的任务继续执行。
* threadFactory:线程的创建工厂。
* RejectedExecutionHandler handler:如果队列满了,按照我们指定的拒绝策略拒绝执行任务
*
*
* //自建线程池,
* ThreadPoolExecutor executor = new ThreadPoolExecutor(
*                 5,
*                 200,
*                 10,
*                 TimeUnit.SECONDS,
*                 new LinkedBlockingDeque<>(100000),  //默认是Integer的最大值,直接new内存不够
*                 Executors.defaultThreadFactory(),
*                 new ThreadPoolExecutor.AbortPolicy()); // 如果不想抛弃,默认运行,使用CallerRunsPolicy
*
* 工作顺序:
* 1)、线程池创建,准备好core数量的核心线程,准备接受任务
* 1.1、core满了,就将再进来的任务放入阻塞队列中。空闲的core就会自己去阻塞队列获取任务执行
* 1,2、阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
* 1,3、max满了就用RejectedExecutionHandler拒绝任务
* 1.4、max都执行完成,有很多空闲.在指定的时间keepAliveTime以后,释放 当前线程数-core这些线程
*
* 面试题:
* 一个线程池core7,max20,queue50,100并发进来怎么分配的;
* 7个会立即得到执行,50个会进入队列,再开13个进行执行。剩下的30个就使用拒绝策略。|
*
* Executors里面常用的线程池:
* Executors.newCachedThreadPool()      core是8,所有都可回收
* Executors.newFixedThreadPool()       固定大小,core=max; 都不可回收
* Executors.newScheduLedThreadPool()   定时任务的线程池
* Executors.newSingleThreadExecutor()  单线程的线程池,后台从队列里面获取任务,挨个执行
*/

线程池配置类,方便在应用中使用
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {

    @Bean
    public ThreadPoolExecutor executor(ThreadPoolConfigProperties pool) {
        return new ThreadPoolExecutor(
                pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

}
第二个配置类,绑定配置文件
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "gulimall.thread")
// @Component
@Data
public class ThreadPoolConfigProperties {

    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAliveTime;
}
配置文件
#配置线程池
gulimall.thread.coreSize=20
gulimall.thread.maxSize=200
gulimall.thread.keepAliveTime=10

CompletableFuture 调用线程池执行任务 + 异步编排

System.out.println("main...start...");


        // 没有返回值的 runAsync
        //CompletableFuture.runAsync(()->{
        //    System.out.println("当前线程:" + Thread.currentThread().getId());
        //    int i = 10 / 2;
        //    System.out.println("运行结果:" + i);
        //},executor);


        /**
         * 方法执行后的感知  supplyAsync whenComplete exceptionally
         */ 
        //CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        //    System.out.println("当前线程:" + Thread.currentThread().getId());
        //    int i = 10 / 2;
        //    System.out.println("运行结果:" + i);
        //    return i;
        //}, executor)
        //        // 运行完前面的方法调用这个 不能return值
        //        .whenComplete((res,exception)->{
        //            System.out.println("异步任务完成,结果:"+res+";异常:"+exception);
        //        })
        //        // 出现异常后执行,可以修改返回值
        //        .exceptionally(throwable -> {
        //            return 10;
        //        });


        /**
         * 方法执行后的处理 handle
         */
        //CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        //    System.out.println("当前线程:" + Thread.currentThread().getId());
        //    int i = 10 / 2;
        //    System.out.println("运行结果:" + i);
        //    return i;
        //}, executor)
        //        // 运行完前面的方法调用这个 可以return值,对前面方法的结果进行修改
        //        .handle((res,thr)->{
        //           if(res!=null){
        //               return res*2;
        //           }
        //           if(thr!=null){
        //               return 0;
        //           }
        //           return 0;
        //        });




        /**
         * 一个方法执行后执行其他任务
         * 1.thenApplyAsync:能获取上一个方法的结果,能返回值
         * 2.thenAcceptAsync:能获取上一个方法的结果,不能返回值
         * 3.thenRunAsync:不能获取上一个方法的结果,不能返回值
         */
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).thenApplyAsync(res -> {
            return "任务2" + res;
        }, executor);
        // 使用get方法获取返回值
        //Integer integer = future.get();
        String s = future.get();
        System.out.println("main...end..."+ s);




        /**
         * 两个方法执行之后执行第三个任务
         * 1.thenCombineAsync  能获取前两个返回值,也能return
         * 2.thenAcceptBothAsync
         * 3.runAfterBothAsync
         */
        CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
                System.out.println("任务1开始:" + Thread.currentThread().getId());
                int i = 10 / 2;
                System.out.println("任务1结束:" + i);
                return i;
            }, executor);


        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2开始:" + Thread.currentThread().getId());
            System.out.println("任务2结束:");
            return "hello";
        }, executor);


        CompletableFuture<String> future03 = future01.thenCombineAsync(future02, (f1, f2) -> {
            return f1 + "+" + "f2 -> ddd";
        }, executor);


        String x = future03.get();
        System.out.println("main...end..."+ x);






        /**
         * 两个方法中执行完一个,开始执行
         * 1.applyToEitherAsync  获取其中一个返回值,可以return【要注意前面两个方法的返回值要一样】
         * 2.acceptEitherAsync
         * 3.runAfterEitherAsync
         */


        CompletableFuture<Object> future11 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1开始:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("任务1结束:" + i);
            return i;
        }, executor);


        CompletableFuture<Object> future12 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2开始:" + Thread.currentThread().getId());
            System.out.println("任务2结束:");
            return "hello";
        }, executor);


        CompletableFuture<String> future13 = future11.applyToEitherAsync(future12, (res) -> {
            return res.toString() + "www";
        }, executor);


        String y = future13.get();
        System.out.println("main...end..."+ y);


        /**
         * 所有任务执行完后执行        allOf
         *      【可以用这个等待需要多个任务执行后的结果,这是并行等待,而不是一个一个地等每个任务执行的结果】
         * 其中一个任务执行完后执行     anyOf  调用 get方法获得最快执行完的结果
         */
        CompletableFuture<String> future21 = CompletableFuture.supplyAsync(() -> {
            System.out.println("111");
            return "1";
        }, executor);


        CompletableFuture<String> future22 = CompletableFuture.supplyAsync(() -> {
            System.out.println("222");
            return "2";
        }, executor);


        CompletableFuture<String> future23 = CompletableFuture.supplyAsync(() -> {
            System.out.println("333");
            return "3";
        }, executor);


        CompletableFuture<Void> allOf = CompletableFuture.allOf(future21, future22, future23);
        allOf.get();
        System.out.println("allOf 3个任务的结果:"+future21.get()+future22.get()+future23.get());


        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future21, future22, future23);
        System.out.println("anyOf "+anyOf.get());

ThreadLocal同一线程共享数据

它可以用来存session 或者 单个用户的信息

ThreadLocal的几种误区:https://www.cnblogs.com/firstdream/p/7886480.html

每一个请求过来,tomcat都会开一个线程,ThreadLocal 只有这个线程内才能共享数据,是Java本身自带的

tomcat类似于线程池,有核心线程和最大线程,当请求过多,也会先把请求存起来。请求过多,则拒绝访问

而 ThreadLocal 是针对一个线程的,因为 tomcat里面的 线程可能重复地用,所以存数据到里面之后,要记得删除,以免被其他用户使用

​ 而且为了防止内存泄漏,也需要删除数据:https://zhuanlan.zhihu.com/p/58636499

把用户信息存起来,然后在 Controller 到 dao 方法都可以调用

要注意在 方法调用完后 删除 ThreadLocal 里面的内容,不然可能会有内存泄漏

ThreadLocal工具类
public class UserThreadLocal {

    private UserThreadLocal(){}

    private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();

    public static void put(SysUser sysUser){
        LOCAL.set(sysUser);
    }

    public static SysUser get(){
        return LOCAL.get();
    }

    public static void remove(){
        LOCAL.remove();
    }
}
拦截器
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {


    @Autowired
    private LoginService loginService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /**
         * 1.需要判断 请求的接口路径 是否为 HandlerMethod (就是Controller方法)
         *      【目的是为了放行 访问静态资源的方法。尚硅谷是在MVCConfig里面设置拦截规则的,如下】
         *      @Override
         *      public void addInterceptors(InterceptorRegistry registry) {
         *         registry.addInterceptor(new LoginInterceptor())
         *                 .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
         *                 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
         *                         "/js/**","/aa/**"); //放行的请求
         * 2.判断token是否为空,如果为空,未登录【只是演示一下。因为前端不是每个请求的Header都带 Authorization 通过不了拦截器】
         * 3.如果token不为空,验证登录 LoginService checkToken
         * 4.如果认证成功 放行即可
         */
        if(!(handler instanceof HandlerMethod)){
            //放行静态资源
            return true;
        }


        //2.判断token是否为空,如果为空,未登录
        String token = request.getHeader("Authorization");

        if(StringUtils.isBlank(token)){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }


        //3.如果token不为空,验证登录 LoginService checkToken
        SysUser sysUser = loginService.checkToken(token);
        if(sysUser == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        // 登录验证成功 放行
        // 希望可以直接在Controller获取用户信息
        // 【上面调用的loginService.checkToken其实已经从redis中获取用户信息了,捂脸,
        // 只是不能直接return,写到session其实应该也可以,为什么用UserThreadLocal】
        //      欧~,原来session其实只是在浏览器的cookie存一个id,实际上的数据还在在服务器端,很多人都是把session的值放在redis里面,有很多好处
        //      这里的话,自然不合适存session,不然本质上其实还是存redis,刚刚从redis拿出来又存进去有点,捂脸
        //ThreadLocal是单单这个线程可以用,就是说如果其他用户也在验证登录,那每个用户获得的用户信息都是自己的,感觉是做了一个区分
        UserThreadLocal.put(sysUser);
        return true;
    }


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 如果ThreadLocal中用完的信息不进行删除,会有内存泄漏的风险
        UserThreadLocal.remove();
    }
}
使用

​ 可以在Controller使用,也可以在 service dao 使用

@RestController
@RequestMapping("test")
public class TestController {


    @RequestMapping
    public Result test(){
        SysUser sysUser = UserThreadLocal.get();
        System.out.println(sysUser);
        return Result.success(sysUser);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沛权

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值