栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程面试题

线程
  • 1 .线程的生命周期?线程有几种状态?
  • 2.sleep()、wait()、join()、yield()?
  • 3.对线程安全的理解?
  • 4.多线程的实现方式?
  • 5.简述线程池?
  • 6.线程复用的原理?
  • 7.线程池:三大方法&七大参数&四大拒绝策略?
  • 8.线程池中阻塞队列的作用?为什么先添加到队列而不是先创建最大线程?
  • 9.线程池的最大大小(maximumPoolSize)应该如何去设置?
  • 10.谈一谈ThreadLocal?
  • 11.内存泄露&四中引用?
  • 12.并发、并行、串行?
  • 13.并发三大特性:原子性、可见性、有序性?
  • 14.volatile关键字?
  • 15.什么是指令重排?

1 .线程的生命周期?线程有几种状态?

1.线程有五种状态:创建、就绪、运行、阻塞、死亡
2.阻塞又分为三种:

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法
同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池
其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):创建一个线程
2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权
3.运行状态(Running):就绪的线程获取CPU执行权,运行代码
4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行
5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期。

简述java线程的状态

java线程的状态有六种:

新建(New):新建状态,线程被创建且未启动,此时还未调用 start 方法。
运行(Runnable):运行状态。其表示线程正在JVM中执行,但是这个执行,不一定真的在跑,也可能在排队等CPU。
阻塞(Blocked):线程等待获取锁,锁还没获得。
等待(waiting):等待状态。线程内run方法运行完语句Object.wait()/Thread.join()进入该状态
限期等待(timed_waiting):限期等待。在一定时间之后跳出状态。调用Thread.sleep(long) Object.wait(long) Thread.join(long)进入状态。其中这些参数代表等待的时间。
结束(terminated):结束状态。线程调用完run方法进入该状态。

2.sleep()、wait()、join()、yield()?

1.sleep是Thread类的方法,wait()是Object类的方法
2.sleep方法不会释放lock,但是wait会释放,而且会加入等待队列
3.sleep方法不需要依赖synchronized,可以在任何地方使用,但是wait方法需要依赖synchronized
4.sleep用于当前线程的休眠,wait用于多线程之间的通信
5.sleep会让出cpu并强制上下文切换,而wait不一定,wait后还是有机会重新竞争到锁继续执行。

yield()执行后直接进入就绪状态,释放cpu执行权,但保留cpu执行资格,有可能下次调度还会让该线程获取到执行权继续执行

join()执行后进入阻塞,例如在线程B中调用线程A的join(),线程B进入阻塞,直到线程A结束或中断线程

3.对线程安全的理解?

不是线程安全,应该是内存安全,堆是共享内存,可被所有线程访问。
当多个线程访问一个对象时,如果不进行额外的同步控制或者协调操作,通过调用这个对象的行为都可以获取正确的结果,我们就说这个对象是线程安全的。
堆是进程和线程共有的空间,分全局堆和局部堆,全局堆是未分配的空间,局部堆是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行时可以像系统索取额外的堆,用完要还给操作系统,不然就是内存泄露。
Java中,堆是Java虚拟机管理的内存最大一块,所有线程共享的内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放实例,几乎所有对象实例以及数组都在这里分配内存。

栈是每个线程锁独有的,每个线程的栈相互独立,是线程安全的

目前主流操作系统都是多任务的,为保证安全,每个进程只能访问分配给自己的内存空间,不能访问别的进程。但是在每个进程的内存空间中都有一块公共区域,即堆,进程内所有线程都可以访问到该区域,这就是问题的潜在原因。

4.多线程的实现方式?

1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、实现Callable接口通过FutureTask包装器来创建Thread线程
4、使用线程池接口ExecutorService结合Callable、Future实现有返回结果的线程

5.简述线程池?

缺点:没有线程池的情况下,多次创建,销毁线程开销比较大。
线程池的作用:线程复用、节省资源、方便控制管理、控制最大并发数
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。

线程池执行任务:
1.核心线程池未满,创建一个新的线程执行任务
2.核心线程池已满,工作队列未满,将线程存储在工作队列
3.工作队列已满,线程池小于最大线程数就创建一个新线程处理任务
4.超过最大线程数,按照拒绝策略处理任务

6.线程复用的原理?

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,核心原理在于线程池对线程封装成工作线程worker,并不是每次执行任务都调用start()方法,而是去让线程执行一个循环任务,在循环任务中不停检查是否有任务需要执行,如果有则通过调用run()方法来执行。

7.线程池:三大方法&七大参数&四大拒绝策略?

三大方法:使用Executors工具类创建线程池

单个线程:Executors.newSingleThreadExecutor()
创建一个固定的线程池大小:Executors.newFixedThreadPool(5)
可动态调节的线程池:Executors.newCachedThreadPool()
七大参数:使用ThreadPoolExecutor创建线程池

corePoolSize:常驻核心线程数。超过该值后如果线程空闲会被销毁。
maximumPoolSize:线程池能够容纳同时执行的线程最大数。
keepAliveTime:线程空闲时间,线程空闲时间达到该值后会被销毁,直到只剩下 corePoolSize 个
线程为止,避免浪费内存资源。
4.TimeUnit:空闲时间单位(TimeUnit.SECONDS)
workQueue:工作队列(常用阻塞队列–new linkedBlockingQueue<>())
threadFactory:线程工厂,用来生产一组相同任务的线程(Executors.defaultThreadFactory())
handler:拒绝策略。有以下几种拒绝策略:
四大拒绝策略

1.AbortPolicy:丢弃任务并抛出异常
2.DiscardPolicy 表示直接抛弃当前任务但不抛出异常。
3.DiscardOldestPolicy 抛弃队列里等待最久的任务并把当前任务加入队列
4.CallerRunsPolicy: 重新尝试提交该任务(哪儿来的去哪儿)

8.线程池中阻塞队列的作用?为什么先添加到队列而不是先创建最大线程?

与一般队列的对比:

一般队列只能保持固有长度的缓冲区,如果任务数超出缓冲区,就无法保留当前任务了。
而阻塞队列可以通过阻塞来保留住当前想要继续入队的任务,与此同时,阻塞队列自带阻塞与唤醒功能,当任务队列中没有任务时,线程池通过阻塞队列的take方法挂起,维持核心线程的存活,不至于一直占用cpu。

9.线程池的最大大小(maximumPoolSize)应该如何去设置?

1.CPU密集型:CPU有多少核,就是多少–>
Runtime.getRuntime().availableProcessors()
2.IO密集型: 判断程序中十分耗IO的线程,例如有15个大型任务耗IO资源,一般选择其两倍。

10.谈一谈ThreadLocal?

ThreadLocal是线程共享变量。ThreadLocal有一个静态内部类ThreadLocalMap,其Key是ThreadLocal对象,值是Entry对象,ThreadLocalMap为每个线程私有。


ThreadLocal存在内存泄露问题:

由于ThreadLocalMap的生命周期与Thread一样长,如果没有手动删除对应Key就会导致内存泄露,而不是因为弱引用
解决方法:

1.每次使用完,ThreadLocal调用remove()方法清除数据
2.将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal强引用,保证能通过Key访问到Value值,进而清除。

11.内存泄露&四中引用?

内存泄露:不再被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
引用:如果reference类型中的数据中存储的数值是另一块内存的起始地址,就成这块内存代表一个引用

强引用:Java中默认引用是强引用,只要强引用存在,垃圾回收器永远不会回收被引用的对象,哪怕内存不足,只会抛出OOM,不会去回收
软引用:用于描述一些非必须但有用的对象,内存够足时,软引用对象不会被回收,只有在内存不足时,才会回收软引用对象,如果内存还是不够,才会抛出OOM,这种特性使他往往用于实现缓存技术。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
弱引用:比软银用的强度更弱。只要JVM进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用java.lang.ref.WeakReference来表示弱引用。
虚引用:最弱的引用关系。与其他几种不同,不会决定对象的生命周期,而是被用来跟踪对象被垃圾回收器回收的活动,必须与引用队列联合使用。当垃圾回收器准备回收一个对象的时候,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

12.并发、并行、串行?

并行:时间重叠,两个任务在同一时刻互不干扰执行
并发:允许两个任务互相干扰,两者交替执行,同一时间只能有一个任务执行
串行:多个任务挨个执行,时间不可能发生重叠

13.并发三大特性:原子性、可见性、有序性?

原子性:指在一个操作中,cpu不可以中途暂停再调度,即不被中断操作,要么全部执行完成,要么全部执行失败。
可见性:当一个线程修改了共享变量,其他线程能够立即得知被修改过。关键字volatile、synchronized、final
有序性:虽然多线程存在并发和指令优化等操作,但在本线程内观察该线程的所有执行操作都是有序的。

14.volatile关键字?

可见性:当一个线程修改了共享变量,其他线程能够立即得知被修改过
不保证原子性:原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。
禁止指令重排(有序性)

15.什么是指令重排?

在程序执行时,为提高性能,编译器和处理器往往会对指令进行重排,而不是按照原来的顺序执行。
重排序分为以下三种:

1.编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2.指令级并行的重排序:现代处理器采用了指令级并行技术,来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3.内在系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
如何禁止指令重排?

内存屏障:禁止上面的指令与下面的指令顺序进行交换

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/693010.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号