多线程业务场景
实际业务开发中使用到的多线程:
1、短信验证码登录时,一般单独开一个线程去调用这个短信接口,主线程返回。
2、前端经常使用的ajax发送请求方法之所以是异步就是多线程。
3、图片上传业务。
4、文件服务中,对各节点上传文件进行分类,阶段很多,节点很多,速度非常慢,一两百个文件,就耗时700ms,经过使用多线程处理后,几十ms就解决了,效率提高了十倍,美滋滋,用户再也体会不到那种卡顿的感觉了。
框架本身的多线程
1、tomcat以多线程的方式响应请求,上百个客户端访问tomcat,来一个请求,就用一个线程来接。
2、项目连接数据库时经常使用的druid连接池。
多线程常见问题之多线程安全问题
明确一点,不操作共享变量就不会有线程安全问题。
有线程安全问题,什么时候加锁
共享数据有可能同时被两个线程操作,再加锁。
别碰到了多线程就想着线程安全,然后我要加锁,加锁会导致什么后果?效率变低,你用多线程,主要就是提升效率的,你再每个线程上个锁。。总而言之,乱用锁的后果性能降低都是小事,甚至会卡死。
效率和数据安全是个跷跷板,A高了,B就低,合理的运用锁,才是正解。
多线程常见问题之多线程乱跑传参问题
多线程协同问题, 本身多线程是没有执行顺序的。
有的时候会遇到这样的场景,想提高效率,使用了多线程,但是方法与方法之间的传参乱套了,这么多线程齐头乱跑,第二个线程要用到第一个线程的结果,怎么搞。
join()方法:当调用join()方法的线程结束之后,其他线程才可以跑。
上图如下:
再举个例子:
总结:
如果你有两个线程A,线程B,线程B要使用线程A的结果,可以说是线程传参问题,怎么解决?
你可以使用join()方法,但你的执行速度肯定会变慢,那怎么办?
可以取巧一下,打个时间差,根据自己的业务来,不一定非要用户触发再跑多线程,提前跑了完事。
几十ms的时间,用户是感受不到的,但是如果就在这100ms内,多个线程一起跑,你还要利用其他线程的结果,肯定会有问题,计算机是能感受到多少多少ms的。
最简单的多线程操作方法
- 继承Thread类
- 实现Runnable接口,没有返回值,不能抛异常。
- 实现Callable接口,有返回值,可以抛异常。
- 线程池。
第一种:
public class MyThread extends Thread {
public void run() {
System.out.println("我的新线程!");
}
}
使用的时候直接这样也可以,匿名内部类。
第二种:
public class Test implements Runnable {
@Override
public void run() {
System.out.println("我的新线程!");
}
}
第三种:
第四种:
注意一点
Thread类中的start()和run()方法使用的时候不一样
调用start()方法,相当于启动了一个新的线程。
调用run()方法,还是在原来的线程中调用,没有新的线程启动。
线程池简介
使用多线程编程时,线程的数量并不是越多越好,并且一直开新线程,对资源的消耗很大,你处理一段逻辑消耗的资源可能仅仅占用百分之十,而新建线程销毁线程这些生命周期就占据了资源的百分之九十。
所以线程最好可以回收利用,不是说你这个线程执行了这个任务,你就可以go die了,不,你还可以继续给老板挣法拉利。
线程做到了重复使用,但是不能让线程给你瞎搞吧,天天划水怎么办,光吃不干,领导们不喜欢这样的线程,所以线程池们要对你们进行管理,让你打狗不要去撵鸡。
线程的生命周期
线程有生命周期,线程池有状态。
线程有五个阶段,线程池有五个状态。
1、新建
2、就绪
3、运行
4、阻塞
5、终止
线程池状态
RUNNING:接受新任务并处理排队的任务
SHUTDOWN:不接受新任务,但处理排队的任务
STOP:不接受新任务,不处理排队的任务,中断正在进行的任务
TIDYING:所有任务都已终止。
TERMINATED:已完成。
最简单的线程池操作方法
ThreadPoolExecutor类:
·························重点来袭·························
int corePoolSize
核心线程数量,你打算这个线程池根据业务需求,大概需要多少个线程。创建线程池后,池子中线程数量为0,来一个请求,开一个线程。
int maximumPoolSize
线程池最大能创建多少个线程,由这个参数决定。
long keepAliveTime
线程最大空闲时间,这个参数对核心线程无效,核心线程比较厉害,谁都管不了它。只有当前线程>核心线程时,空闲时间才会生效,如果此时某个线程空闲时间达到了这个点,就终止。
TimeUnit unit
时间单位。
TimeUnit.DAYS.
TimeUnit.HOURS.
TimeUnit.MINUTES.
TimeUnit.SECONDS.
TimeUnit.MILLISECONDS.
TimeUnit.MICROSECONDS.
TimeUnit.NANOSECONDS.
BlockingQueue workQueue
线程等待队列,存放等待执行的任务。
ArrayBlockingQueue:基于数组,创建时指定大小,排队的任务是有限度的。
LinkedBlockingQueue:基于链表,无界队列,核心线程已满后,进入的所有任务进入队列,直到资源耗尽。
SynchronousQueue:不是队伍,直接新建线程,执行任务。
ThreadFactory threadFactory
线程创建工厂。
RejectedExecutionHandler handler
拒绝策略,如果当前线程已经达到了最大线程数,则执行拒绝策略。
ThreadPoolExecutor.AbortPolicy:抛出异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新执行任务。
ThreadPoolExecutor.CallerRunsPolicy:调用者线程处理任务
整体流程
上边参数是让你自己设计一个线程池,非常重要的一个点是线程数量。
当前<核心,创建新线程处理请求。
核心<当前>最大,进入等待队列,等待执行。
核心=最大,创建固定大小的线程池。
最大线程数为无穷大,线程池无限制开新线程。
总体流程为:
第一步: 接收请求,判断当前线程数与核心线程数的大小:
·············若小于核心线程数,则开新线程执行任务。
·············若大于核心线程数,则进入等待队列。
第二步:等待队列,判断队列是否满员,这一步很重要,不仅仅是判断队列满员没有,还要看设置的排队策略。
············若未满员,就可以进去,等待执行。
············若满员,进入失败,根据相应的排队策略执行不同的操作。
第三步:判断当前线程数与最大线程数的大小
············若大于最大线程数,则执行拒绝策略。
············若小于最大线程数,则提交任务等待执行。
如果你想省事一点,可以直接使用JDK提供的,明白了这七个参数代表含义,下面这四个也就ok了。
1、newFixedThreadPool
2、newCachedThreadPool
3、newSingleThreadExecutor
4、newSingleThreadScheduledExecutor
线程池关闭方法
1、shutdown:有序的关机,执行方法后,不再接收新任务,队列中任务全部执行完毕后关机。
2、shutdownNow:强制性关闭所有未执行及正在执行的任务,瞬时关机,返回尚未执行的任务。
·············································································
你学废了吗