多线程全解

作为面试时提问的半壁江山,多线程一直是一大考点,写篇详细的全解,等面试的时候吊打面试官!加油,奥利给!

多线程全解

一、线程的几种状态

  • New 新建状态
  • Runnable 运行状态
  • Blocked 阻塞状态(.sleep 抱着锁睡;.await 放开锁等待;)
  • Waiting 等待(死等,不见不散)
  • Timed_Waiting 等待(等一会儿,过时不候)
  • Terminated 结束

二、创建线程的几种方式

  • 继承Thread类

  • 实现Runnable接口,实现run()方法(最简单,最常用)
    示例:new Thread(()->{}, String.valueOf(1)).start();

  • 实现Callable接口,实现call()方法(使用一个FutureTask类做中间类来实现)

  • 使用线程池获取线程(阿里规范推荐)

三、线程池获取线程

获取线程池

1.定长线程池,指定线程池中的线程个数

ExecutorService executorService = Executors.newFixedThreadPool(5);

缺点:队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。
适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。

2.单线程池,线程池中仅维护一个线程

ExecutorService executorService = Executors.newSingleThreadExecutor();

说明:单线程池其实就是长度为1的定长线程池!并在其代码中加了一层FinalizableDelegatedExecutorService包装,使他不能转型为ThreadPoolExecutor保证线程是‘单一’的

3.自适应线程池,线程池根据线程的使用量自动创建和回收线程

ExecutorService executorService = Executors.newCachedThreadPool();

缺点:maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制,容易造成内存溢出。
适用场景:快速处理大量耗时较短的任务,如Netty的NIO接受请求时,可使用CachedThreadPool。

4*.自定义线程池,使用5参构造或7参构造新建 ThreadPoolExecutor(),阿里规范推荐此方式创建线程。

ExecutorService executorService = new ThreadPoolExecutor(2, 5,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

说明:通过自定义线程池,我们可以更好的让线程池为我们所用,更加适应我的实际场景。

注意,无论是使用哪种线程池,用完都要关掉!

try{
	多线程使用.....
}catch{
	
}finally{
	executorService.shutdown();
}

通过线程池获取、使线程

1.execute()

executorService.execute(()->{System.out.println(Thread.currentThread().getName()); });

2.submit()

Future future = executorService.submit(()->{System.out.println(Thread.currentThread().getName()); });

3.区别
submit方法其实底层使用的还是execute方法,只不过在之上封装了返回值,可以通过future.get()获取线程执行结果【注意:如果future.get()==null,则说明线程正常结束】,由于需要自己调结果不会抛出异常,有时候会导致线程被挂起而不报错的情况;
execute是一个返回void方法,但是他在运行异常时会直接抛出异常;
在这里插入图片描述

创建线程池的参数分析

int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler

1、corePoolSize:线程池中的核心线程数;
2、maximumPoolSize:线程池能容纳的最大线程数,必须大于等于1;
3、keepAliveTime:多余线程的存活时间,当前线程池中的线程数大于核心线程数corePoolSize,且空闲时间达到存活时间keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止;
4、TimeUnit :keepAliveTime的单位;
5、BlockingQueue:阻塞队列,用来存放被提交但尚未执行的任务,在此队列中等待;
6、ThreadFactory :用于生成线程池中的工作线程的线程工厂,用来创建线程,一般默认即可;
7、RejectedExecutionHandler:拒绝策略,表示当前工作线程达到了最大线程数maximumPoolSize,且阻塞队列也满了,如何来拒绝请求执行的runnable的策略。

这里有的面试官会问:怎么设计这些参数?
一般,这些参数都是根据业务来设计。其中,最大线程数maximumPoolSize,一般设置为 当前计算机处理器个数+1,即

int maximumPoolSize= Runtime.getRuntime().availableProcessors+1;

线程池的处理流程:
一个任务提交到线程池,

  • 如果当前在执行的线程,少于核心线程数,那么直接取一个线程去运行;
  • 如果线程池中的核心线程个数都在被使用,那么就将当前任务放到阻塞队列中;
  • 如果阻塞队列也满了,但是当前线程数小于最大线程数,那么就创建新线程来处理阻塞队列中的任务,并将当前任务放入阻塞队列;
  • 如果阻塞队列满了,线程池也达到了最大线程数,那么就执行拒绝策略;
  • 当一个线程完成当前任务,就会从队列中取下一个任务来执行;
  • 当阻塞队列中的任务被处理完了,且当一个线程无事可做的时间超过keepAliveTime时,线程池判断当前线程是否大于核心线程数,如果大于,那么这个线程将会被销毁,直到恢复到核心线程数。

拒绝策略

1、AbortPolicy:直接抛出RejectedExecutionException异常,中断系统正常运行,默认使用该策略。
2、CallerRunsPolicy:“调用者运行”一种机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的溜了。
简单来说,谁指派任务给我的,我再回退给谁,让他自己处理,例如main方法调用线程池,线程池满了,那么就由main自己来处理该任务。
3、DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常,如果当前任务允许失败,这是最好的一种策略。
3、DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中,等待再次提交当前任务。

四、JUC对于多线程的三个辅助类

CountDownLatch(计数器递减,确保有一个线程必定在最后才执行)

举例:考试。
有5个学生考试,一个老师在监考,学生可以提前交卷,但是老师必须所有学生的试卷收齐后才能走。即,5个线程在运行,第6个线程必须在5个线程执行完之后才能执行。
在这里插入图片描述

CyclicBarrier(计数器递增,当计数器达到多少时才执行指定线程)

举例:开会。
开发小组有5个人,约定下午两点会议室开会,可能前几个人早点到了,但是必须等最后一个人到了之后才能开始正式开会,否则一直等着。即,每一个线程执行完计数器加一,直到5个线程都执行完,开启额外的第六个线程。
在这里插入图片描述

Semaphore(多线程竞争多资源,用于多线程的并发控制)

举例:抢车位。
公司楼下有3个车位,但是公司里有6辆车,所以先到先得,没抢到车位的就等着别人走了才能进去。即,6个线程竞争3个资源,控制线程的并发量。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值