多线程和线程池的使用
/**
* 启动方式+定义线程
* 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);
}
}