多线程编程,都有哪些实际应用场景?

多线程业务场景

实际业务开发中使用到的多线程:

1、短信验证码登录时,一般单独开一个线程去调用这个短信接口,主线程返回。

2、前端经常使用的ajax发送请求方法之所以是异步就是多线程。

3、图片上传业务。

4、文件服务中,对各节点上传文件进行分类,阶段很多,节点很多,速度非常慢,一两百个文件,就耗时700ms,经过使用多线程处理后,几十ms就解决了,效率提高了十倍,美滋滋,用户再也体会不到那种卡顿的感觉了。

框架本身的多线程

1、tomcat以多线程的方式响应请求,上百个客户端访问tomcat,来一个请求,就用一个线程来接。

2、项目连接数据库时经常使用的druid连接池。

多线程常见问题之多线程安全问题

明确一点,不操作共享变量就不会有线程安全问题。

有线程安全问题,什么时候加锁

共享数据有可能同时被两个线程操作,再加锁。

别碰到了多线程就想着线程安全,然后我要加锁,加锁会导致什么后果?效率变低,你用多线程,主要就是提升效率的,你再每个线程上个锁。。总而言之,乱用锁的后果性能降低都是小事,甚至会卡死。

效率和数据安全是个跷跷板,A高了,B就低,合理的运用锁,才是正解。

多线程常见问题之多线程乱跑传参问题

多线程协同问题, 本身多线程是没有执行顺序的。
有的时候会遇到这样的场景,想提高效率,使用了多线程,但是方法与方法之间的传参乱套了,这么多线程齐头乱跑,第二个线程要用到第一个线程的结果,怎么搞。

join()方法:当调用join()方法的线程结束之后,其他线程才可以跑。

上图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
再举个例子:
在这里插入图片描述
总结:

如果你有两个线程A,线程B,线程B要使用线程A的结果,可以说是线程传参问题,怎么解决?

你可以使用join()方法,但你的执行速度肯定会变慢,那怎么办?

可以取巧一下,打个时间差,根据自己的业务来,不一定非要用户触发再跑多线程,提前跑了完事。

几十ms的时间,用户是感受不到的,但是如果就在这100ms内,多个线程一起跑,你还要利用其他线程的结果,肯定会有问题,计算机是能感受到多少多少ms的。

最简单的多线程操作方法

  1. 继承Thread类
  2. 实现Runnable接口,没有返回值,不能抛异常。
  3. 实现Callable接口,有返回值,可以抛异常。
  4. 线程池。

第一种:

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:强制性关闭所有未执行及正在执行的任务,瞬时关机,返回尚未执行的任务。

·············································································
你学废了吗

猜你喜欢

转载自blog.csdn.net/numbbe/article/details/109313814