Java 面试多线程刷题

面试== 基础 + 项目 + 算法

基础 = Java基础、多线程、分布式、JVM、集合类、Spring、数据库等, 以问答形式为主, 需要熟记考点。

项目 = 网上跟着做的项目,能说清楚,具有一定的复杂性和工程性。

算法 = 剑指offer + Leetcode 面试原题 + 低配ACM笔试题,笔试题只需要及格即可,难度偏大,刷简历,想办法海投内推。

 

如何理解线程和进程的区别

进程:

  • 进程是操作系统结构的基础
  • 程序在一个数据集合上运行的过程
  • 系统进行资源分配和调度的独立单位

线程:

  • 轻装进程
  • 进程中独立运行的子任务
  • CPU调度和分派的基本单位
  • 基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  • 一个程序至少有一个进程,一个进程至少有一个线程
  • 线程的划分尺度小于进程,使得多线程程序的并发性高
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
  • 多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配
  • 线程的创建和切换开销比进程小

 

协程是用户态的线程,没有上下文切换

 

进程的调度方式

基本方式:剥夺/非剥夺
基本原则:CPU利用率;系统吞吐量;周转时间;等待时间;响应时间
实际方案:

  • 先来先服务
  • 短作业优先
  • 高响应比
  • 时间片轮转
  • 多级反馈队列

 

进程通信的方式

  • 共享存储,通过一片共享区域进行读写操作实现进程之间的信息交换。在操作时需要使用同步互斥工具(PV操作)。共享存储分为低级的基于数据结构的共享和高级的基于存储区的共享
  • 消息传递,利用os提供的发送原语和接收原语进行交换。消息传递分为直接通信和间接通信。直接通信采用进程间发消息的方式,将消息挂在接收进程的消息缓冲队列上,接收进程从该队列上取得消息。间接通信则将消息发送到某个中间实体上,称其为信箱。
  • 管道,是指用于连接一个读进程和一个写进程以实现他们之间通信的一个共享文件——.pipe文件。写进程向管道以字符流写入数据,读进程从管道中接收输出。管道必须提供同步、互斥并能确定对方存在的方法
  • 信号量机制,信号量的本质就是计数器,在访问临界资源并进入临界区时,使用os提供的PV操作对计数器进行修改

 

线程通信的方式

  • 等待(wait)/通知机制(notify)
  • 使用方法join
  • 管道输入输出流,使用内存作为传输媒介
  • 生产者-消费者问题模型
  • 使用condition控制线程通信
  • 使用阻塞队列(BlockingQueue)控制线程通信

 

线程中几个常见方法的区别

yield() 与 wait()

  • wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,yield()是让线程由“运行状态”进入到“就绪状态”
  • wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁

sleep() 与 wait()

  • wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”
  • wait()会释放对象的同步锁,而sleep()则不会释放锁
  • sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然 保持者,当指定的时间到了又会自动恢复运行状态;而wait()也会让出CPU,但是必须经过notify()或notifyAll()后才能参与竞争CPU
  • sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用
  • sleep是Thread类的方法,wait是Object类中定义的方法

run() 与 start()

  • run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的
  • 用start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

join(long)与sleep(long)

join(long)在内部使用wait(long)实现,故join(long)具有释放锁的特点。sleep(long)不释放锁。

 

多线程一定比单线程好?

多线程虽然可以带来更好的并发能力,但是并发编程并不能提高程序运行的速度,还会带来很多衍生问题,例如:内存泄漏、上下文切换、死锁等问题。单核运行多线程程序,在同一时间仅由一个线程在运行。此外,系统还要频繁地切换上下文,带来巨大的开销,效率反而更低。

 

什么是下上下文切换?

当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
切换流程:

  • 挂起某进程,将该进程在CPU中的状态(上下文)存储于内存中的某处
  • 在内存中检索下一个进程的上下文并将其在CPU的寄存器中恢复
  • 跳转到程序计数器所指向的位置,以恢复该进程在程素中

切换原因:

  • 在当前执行任务的时间用完后,系统CPU正常调度下一个任务
  • 当前执行任务遇到IO阻塞,调度器将当前任务挂起,执行下一个任务
  • 多个任务抢占锁资源,当前任务没抢到锁资源,被调度器挂起,继续下一任务
  • 用户代码挂起当前任务,让出CPU时间
  • 硬件中断

 

如何减少上下切换的方法

  • 基于无锁并发编程
  • CAS算法
  • 使用最少线程
  • 协程

 

程死锁与死锁的避免方式

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。避免方式:

  • 破坏互斥条件:不靠谱
  • 破坏请求与保持条件:一次性申请所有资源
  • 破坏不剥夺条件:申请不到新资源时,主动释放已占有的资源
  • 破坏循环等待条件:按某一顺序申请资源,释放资源则反序释放

 

死锁的预防&解决

  • 超时法
  • 银行家算法

银行家算法:当一个进程申请使用资源的时候,银行家算法通过先 试探 分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。

 

什么是线程池

线程池(Thread Pool)可用于限制应用程序中同一时刻运行的线程数。每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存,频繁执行这一操作可能会导致性能损耗。我们可以把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。只要池里有空闲的线程,任务就会分配给一个线程执行。在线程池的内部,任务被插入一个阻塞队列(Blocking Queue ),线程池里的线程会去取这个队列里的任务。当一个新任务插入队列时,一个空闲线程就会成功的从队列中取出任务并且执行它。线程池经常应用在多线程服务器上。每个通过网络到达服务器的连接都被包装成一个任务(Task)并且传递给线程池。

 

线程池的基本组成

  • 线程池管理器
  • 工作线程
  • 任务接口
  • 任务队列

 

线程池分别适合什么场景

  • newSingleThreadExecutor:单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务具有有序性
  • newFixedThreadExecutor(n):固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
  • newCacheThreadExecutor:可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行
  • newScheduleThreadExecutor:大小无限制的线程池,支持定时和周期性的执行线程

 

 

线程池当队列中的任务都执行完毕之后会对线程进行什么操作

对于Cache类型的线程池,当前线程数大于corePoolSize时,会根据超时时间逐步缩减线程总数,直至线程总数为corePoolSize。

ThreadLocal是什么?

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。ThreadLocalMap用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

ThreadLocal的应用场景

  • 多线程场景下,每个线程需要单独的变量实例
  • 存储用户Session
  • 解决线程安全问题

ThreadLocal可能存在的问题

ThreadLocal中的key基于弱引用,而value则基于强引用。当发生GC时,可能会出现value强引用保留,key弱引用被回收。解决方法是手动调用remove()方法清除不使用的kv对。

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值