【面试八股|JAVA多线程】JAVA多线程常考面试题详解

这里根据个人说话口吻等编写JAVA多线程常见面试题用于记录复习,后续会持续更新补充,欢迎点赞收藏。

多线程

线程基础知识

线程与进程区别

1.进程是正在运行的线程实例,进程里包含了线程,每个线程执行不同任务

2.不同进程使用不同内存空间,同一进程的所有线程共享内存空间

3.线程更轻量,上下文切换成本较低

并行与并发的区别

并行是同一时间处理多个任务的能力,比如四核cpu同时执行四个线程

并发是同一时间应对多个任务的能力,比如一个cpu被多个线程轮流使用

创建线程四种方式

1.继承thread类

2.实现runnable接口

3.实现callable接口

4.线程池创建线程

runnable与callable接口区别

1.runnable接口run方法没有返回值。callable接口call方法有返回值,是个泛型,可通过futuretask配合获取异步执行结果

2.runnable接口run方法的异常只能内部消化,不能上抛,callable接口的call方法允许抛出异常

3.callabe接口获取执行结果,需要通过futuretask.get()得到,但会阻塞主线程

futuretask的作用

1.thread类只接受runnable,futuretask实现runnable来封装callable

2.实现future,提供管理callable任务的能力

run和start的区别

start原来启动线程,通过该线程调用run方法逻辑,只能调用一次。

run则封装了被线程执行的代码,可以被调用多次

线程有哪些状态,之间如何变换的

1.在thread类中的枚举state中定义了六种状态,分别是:新建,可运行,终结,阻塞,等待,有时限等待

2.当线程对象被创建但为start则处于新建状态,当线程start则进入可运行状态,当线程内代码执行完毕则进入终结状态

3.当线程获取锁失败,则由可运行转换为阻塞状态,如果后续获取了锁则再加入可运行状态

4.当线程获取锁,但由于条件不满足,调用了wait方法,则进入等待状态。当其它持锁线程调用了notify或notifyall,则再恢复为可运行状态

5.当线程获取锁,但由于条件不满足,使用sleep方法或传参的wait方法则进入有时限等待状态。在时间结束后会再重新进入可运行状态

如何保证新建的多个线程顺序执行

可通过线程类的join方法在一个线程中启动另一个线程,另一个线程完成后该线程才会执行,以此控制顺序

notify与notifyall的区别

notify,随机唤醒一个线程

notifyall,唤醒所有wait线程

在 java 中 wait 和 sleep 方法的不同

共同点:都是让线程暂时放弃cpu使用权,进入阻塞状态

不同点:

方法归属不同:sleep是Thread的静态方法。wait是object成员方法,每个对象都有。

醒来时机不同:wait有无参方法和有参方法,wait方法可以被notify唤醒,无参构造如果不被唤醒会一直等下去。sleep无法被notify唤醒。两个都可以被打断唤醒。

锁特性不同:wait调用必须获取对象锁,执行后会释放对象锁。sleep不需要提前获取对象,执行后也不会释放对象锁。

如何停止一个正在运行的线程?

1.使用退出标志,在run方法完成后线程终止

2.使用interrupt方法中断线程

3.使用stop方法强行终止(已废除)

线程中并发锁

synchronized关键字底层原理

第一,synchronized底层使用了jvm级别的monitor来管理当前线程是否获取锁。属于悲观锁并且相对性能较低。

第二,monitor存在于每个java对象的对象头中,也因此java任何对象都可以作为锁。

第三,monitor内部维护了三个变量,分别是waitset,entrylist,owner。当线程获取锁,则会关联owner队列。当线程阻塞,则会关联到entryset。当对象等待时,则关联到wait队列中。

第四,当获取锁的对象释放锁后,entryset等待的线程将竞争锁,此过程是非公平的

synchronized锁进阶原理

第一,synchronized对象锁有偏向锁,轻量级锁,重量级锁三种形式。分别对应单个线程持有锁,多个线程交替持有锁,多个线程竞争锁的情况

第二,重量级锁通过Monitor实现,需要用户态与内核态的切换,进程上下文切换,成本较高,性能较低

第三,轻量级锁借助LockRecord实现,通过cas操作修改对象头锁标志,大幅提高性能。

第四,偏向锁只有第一次获取锁时是cas操作,后续再获取锁只需要判断对象头中是否保存为当前线程id即可。

第五,如果发生锁竞争都会升级为重量级锁

对象内存结构

对象在内存中的存储可被分为三部分:对象头,实例数据,对齐数据(为了使整体达到8的整数倍)

对象头(MarkWord)又在不同锁的情况下被划分为不同部分

  • hashcode:25位的对象标识Hash码

  • age:对象分代年龄占4位

  • biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁

  • thread:持有偏向锁的线程ID,占23位

  • epoch:偏向时间戳,占2位

  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位

  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位

  • 标记为gc表示该对象将被回收

ReentrantLock实现原理

第一,ReentrantLock底层使用CAS+AQS队列实现,提供state表示资源状态,exclusiveownerthread表示获取锁的线程,以及fifo队列表示阻塞线程

第二,reentrantlock是可重入锁,再次调用lock不会阻塞而是内部添加重入次数

第三,reentranlock支持公平锁与非公平锁,构造方法接受可选的公平参数,设置为true为公平锁,否则为非公平锁

ReentrantLock与synchronized的区别

1.功能层面:两者都是可重入锁,悲观锁,但reentrantlock增加了一些高级功能,如等待可中断,可实现公平锁,可借助condition实现选择性通知

2.语法层面:synchronized是关键字,依赖于jvm实现,退出同步代码块锁会自动释放。reentrantlock是依赖于jdk层面实现,需要手动调用unlock释放锁。

3.性能层面:没有竞争时,synchronized有需要优化比如偏向锁,轻量级锁等。竞争激烈时,reentrantlock实现提供了更好性能

谈谈JMM

第一,jmm是java虚拟机规范中定义的重要的内存模型,主要用来描述线程共享变量的访问,存储,读取等规则。

第二,jmm规范了共享变量,如实例变量类变量,都是存储在主内存中,也就是计算机的ram。

第三,jmm规范了每个线程都有自己的工作内存,线程对变量的操作都必须在自己工作内存中完成,而不能直接读取主内存的变量

第四,jmm规范了不同线程不能直接读取对方工作内存的变量,如果线程间需要传递变量值,这个过程必须通过主内存完成

介绍一下CAS

第一,CAS的全程是compare and swap,先比较再交换。体现的是一种乐观锁思想,在无锁状态下保证线程操作数据的原子性。

第二,在操作共享变量时会使用自旋锁,如果更新失败会不断尝试获取新值,直到更新成功

第三,CAS应用非常广泛,在AQS框架,原子类等地方都有使用。

第四,CAS底层使用的是Unsafe类中的方法,由操作系统提供,非java语言实现

介绍一下volatile

volatile是一个可修饰成员变量,类变量的关键字,主要有两个功能

第一,保证了不同线程对于被修饰变量的可见性。即某线程修改了该变量,volatile将强制将被修改的值写入主存对其它线程可见。

第二,禁止指令的重排序。通过添加内存屏障可禁止内存屏障前后的指令执行重排序优化。

介绍一下AQS

AQS其实就是jdk提高的一个类AbstractQueuedSynchronzier,是阻塞式锁和相关同步器工具的框架,用于提供提供「state 状态管理、FIFO 队列、线程阻塞 / 唤醒」的通用逻辑。

其常见实现类有ReentrantLock(可重入锁),Semaphore(信号量),CountDownLatch(倒计时锁)等。

其内部有个被volatile修饰的state属性来表示资源状态,默认state等于0,表示没有获取锁,state为1时表示获取了锁。通过cas操作修改state保证原子性。

AQS内部还提高了基于FIFO的等待队列管理阻塞线程,通过head指向队列最久的元素,tail指向队列最后一个元素

死锁产生条件是什么

当两个线程互相持有对方需要的锁则会产生死锁现象。

死锁产生的必要条件有,互斥条件,请求和保持条件,不剥夺条件,循环等待条件

如何进行死锁诊断

可以通过jdk自带的工具解决。首先通过jps查看当前java程序运行的进程id,然后通过jstack查看当前进程id,定位出代码具体行号进行解决。

我们也可以通过jconsole,visualvm等可视化工具解决。

导致并发程序出现问题的根本原因是什么

1.与java并发编程的三大特性相关,原子性,可见性,有序性

2.对于原子性,我们可以通过synchronized关键字或lock锁

3.对于可见性,可使用synchronized关键字,lock锁或volatile关键字解决

4.对于有序性,可通过volatile关键字避免指令重排序

线程池

说说线程池核心参数

1.共七个参数分别是:核心线程数,线程最大数量,生存时间,时间单位,阻塞队列,线程工厂,拒绝策略

2.拒绝策略有AbortPolicy直接抛出异常,默认策略,CallerRunsPolicy使用调用者所在线程执行任务,DiacardOldestPolicy丢弃最考前线程,DiscardPolicy直接丢弃任务

3.最常用阻塞队列有两种,ArrayBlockingQueue与LinkedBlockingQueue

说说常用的阻塞队列

常见的有四种ArrayBlockingQueue,LinkedBlockingQueue,DelayedWorkQueue,SynchronousQueue。

1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

ArrayBlockingQueue的LinkedBlockingQueue区别

LinkedBlockingQueue ArrayBlockingQueue
默认无界,支持有界 强制有界
底层是链表 底层是数组
是懒惰的,创建节点的时候添加数据 提前初始化 Node 数组
入队会生成新 Node Node需要是提前创建好的
两把锁(头尾) 一把锁

1.LinkedBlockingQueue默认无界支持有界,ArrayBlockingQueue强制有界

2.ArrayBlockingQueue基于数组实现,因此由于可通过索引访问元素,可能更快。LinkedBlockingQueue基于链表实现,因此其添加和删除元素不需要移动其它元素可能更快。

3.ArrayBlockingQueue使用一把锁控制对队列的访问,读写操作互斥。LinkedBlockingQueue使用两把锁,读写操作不互斥,并发性高

如何确定核心线程数

1.对于高并发,执行时间短的场景,采用cpu+1,减少线程上下文切换

2.对于并发不高,执行时间长的场景,如果是io密集型任务则cpu*2+1,如果是计算密集型任务则cpu+1

3.并发高,业务执行长,则从整体架构方面设计,比如缓存,增加服务器等。

线程池的种类有哪些

newFixedThreadPool 创建一个定长线程池,无救急线程,可控制线程最大并发数,超出的线程会在队列 中等待。

newCachedThreadPool创建一个可缓存线程池,全是救急线程,如果线程池长度超过处理需要,可灵活回 收空闲线程,若无可回收,则新建线程。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

为什么不建议Executors创建线程池

不够灵活,队列长度都是Integer.MAX_VALUE,容易堆积请求导致oom。一般推荐使用threadpoolexecutor创建线程池,将控制权把握在程序员手中

说一说CountDownLatch

countdownlatch用于进行线程的同步协同。

具体使用:

1.构造参数初始化等待计数值

2.await等待计数归零

3.countDown让计数减一

如何控制某个方法运行并发访问线程的数量

通过semaphore类。先通过构造方法传参确定可被访问的线程数量,然后acquire表示请求信号量,当信号量为-1时则阻塞,release表示释放信号量。

介绍ThreadLocal

ThreadLocal 操作当前thread内部的threadlocalmap对象吗。

1.隔离资源对象,使每个线程各用各的资源对象。实现线程内资源共享。

2.ThreadLocal底层维护了一个ThreadLocalMap类型的成员变量。使用set方法时就是把threadlocal,自己作为key,资源对象作为value,存入threadlocalmap里。

3.Threadlocal的key被设计为弱引用,而value是强引用。而ThreadLocal通常是静态的,导致entry的key一直有效,如果不手动释放可能会导致oom

相关推荐
天天爱吃肉82182 小时前
跟着创意天才周杰伦学新能源汽车研发测试!3年从工程师到领域专家的成长秘籍!
数据库·python·算法·分类·汽车
大巨头2 小时前
sql2008 数据库分页语句
数据库
sino爱学习2 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端
m0_715575342 小时前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python
熊延2 小时前
麒麟V10系统安装部署elasticsearch
linux·运维·服务器·elasticsearch·搜索引擎·全文检索
老邓计算机毕设3 小时前
SSM智慧社区家政服务系统80q7o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架
风生u3 小时前
activiti7 详解
java
岁岁种桃花儿3 小时前
SpringCloud从入门到上天:Nacos做微服务注册中心(二)
java·spring cloud·微服务
Word码3 小时前
[C++语法] 继承 (用法详解)
java·jvm·c++