面试----多线程篇

288 阅读11分钟

并行与并发有什么区别

单核CPU

image.png 串行:一个线程执行完再执行另一个线程

并行:多个线程同时执行

由于线程切换速度快,所以宏观上是并行的

并发:类似于串行,实际上也是轮流执行的情况

多核CPU

多核的情况下可以多个线程各自使用一个CPU核心,实现真正意义上的并行,即微观上也是并行的

线程的创建方式

继承Thread类,重写run方法

image.png

实现Runnable接口,重写run方法

将Runnable对象作为参数封装给线程

image.png

实现Callable接口

image.png

需要给定一个泛型(这里以String为例),重写call方法返回的结果类型就是这个泛型,因此这种线程创建方式适用于线程有返回结果的场景,如果要获取线程的返回结果,可以使用.get()方法

具体创建线程是通过将MyCallable对象作为参数封装给FutureTask,然后又将FutureTask作为参数封装给线程

使用线程池创建线程

image.png

拓展

image.png

image.png

线程的状态

image.png

如何保证线程的顺序执行

使用join

image.png

notify()和notifyAll()的区别

image.png

image.png

JM有很多实现,比较流行的就是hotspot,hotspot对notofy0的实现并不是我们以为的随机唤醒,而是先进先出的顺序唤醒。

wait()和sleep()的区别

image.png

调用wait方法必须先加锁,不加锁则报错

image.png

wait方法执行后会释放掉锁,此时其他线程可以获得锁:如图中执行到wait方法后,锁释放了,因此下面的main.debug可以获得锁执行代码,当wait执行结束时又会打印出running...end...

image.png

sleep方法执行后不会释放锁,其他线程无法获得锁

image.png

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

使用退出标志

image.png

image.png

主线程中开启子线程,子线程执行run方法,run方法中根据flag进行循环,执行完一次打印就睡3秒钟

而主线程开启子线程后睡了6秒才更新flag标记,所以在主线程睡的6秒钟,子线程可以进行两次打印,之后由于flag被更新了,循环被打破,因此线程停止

image.png

怎么保证多线程安全

image.png

image.png

Synchronized关键字的底层原理

使用回顾

1730284946688.png

底层汇编

image.png

通过反汇编字节码文件可以发现Synchronized底层使用了monitorenter和monitorexit,需要注意的是这里有两个解锁,最后一个解锁是为了防止代码运行抛异常的时候之前上的锁没有释放掉,因此最后一个解锁起到以防万一的作用

image.png

Owner就是获取锁成功的线程,EntryList就是获取锁失败的线程(阻塞)集合,WaitSet就是调用wait方法后等待的线程集合

基础回答

image.png

进阶原理

image.png

Monitor是jvm级别的(可以理解为内核态),而用户编写的代码属于用户态,两种状态的切换导致性能很低,所以说Monitor实现的锁是重量级锁

偏向锁和轻量级锁是针对于线程没有竞争的情况设计出来的,其中偏向锁是说只有一个线程获取锁,没有竞争对手,所以这个锁只偏向于这个线程;而轻量级锁是说虽然有多个线程,但是这些线程因为某种原因(比如执行顺序),恰好没有发生竞争的情况,一个线程用完锁之后另一个线程才接着获取锁,这种没有竞争的情况下如果用重量级锁就很浪费,所以出现了轻量级锁

image.png

image.png

锁对象关联上Monitor:通过hotspot中对象的内存结构中的对象头的MarkWord里的指针

轻量级锁的执行流程

image.png

image.png

首先Object是锁对象obj的内存结构,当第一次获取锁时,获取锁的线程会新增一个Lock Record记录,其中默认初始使用的是轻量级锁(编号为00),然后线程通过CAS(Compare And Set)操作与obj内存结构中的hashcode age进行交换,如果说CAS失败,说明有其他线程也在获取锁并修改了hashcode age(出现了竞争),此时的轻量级锁就应该升级为重量级锁

image.png

另外,如果是锁重入(如图中的代码),线程会再新增一个Lock Record记录,但这条记录的数据为null,它同样有必要进行CAS操作,但这里的锁重入其实已经修改好了CAS(或者说两次记录的CAS操作修改后是一样的)

轻量级锁的释放:

image.png

在释放锁的时候,如图中为重入锁,线程会首先按与获取锁相反的顺序释放锁,因此先看倒数的Lock Record记录,如果它的数据为null,则直接删除这条记录,如果这条记录有数据,则会与Object的数据再次交换回原来的样子,最后释放锁

偏向锁:

image.png

和轻量级锁的区别在于交换数据中有线程id,再次获取锁的时候只需要判断线程id是否是自己即可,如果是就新增Lock Record记录

进阶回答

image.png

Synchronized的使用方式

修饰代码块

多线程---同一对象

image.png

image.png

多线程---不同对象

image.png

多线程---自定义锁对象

image.png

修饰方法

image.png

image.png

修饰静态方法

image.png

image.png

修饰类

image.png

Jave内存模型(JMM)

image.png

线程内的数据存储在工作内存,不同线程的工作内存不能互相访问(及不同线程的数据不能共享),所以需要用到主内存来进行变量的同步

image.png

CAS

原理

image.png

CAS就是先比较后设值(Compare And Set),如果比较失败,则不断地循环获取被其他线程修改后的值再次进行比较,这种不断循环就是自旋,直到比较成功

由于这种自旋锁并不是真正意义上的加锁,而只是一种循环,所以不会出现线程的阻塞,效率较高,当然如果一直循环下去,也还是会影响效率

底层实现

image.png

通过操作系统底层的CAS指令实现

乐观锁、悲观锁

image.png

image.png

CAS缺点、为什么不是所有锁都是用CAS

image.png

image.png

回答

image.png

volatile的理解

理解

Java并发编程的特性之一:可见性

volatile关键字用来修饰变量,可以使变量在线程之间相互可见,即变量的变化是同步的

image.png

在没添加volatile关键字以及指定运行参数的情况下,线程间变量不可见的原因是jit多此一举地给代码做了优化

禁止指令重排序:

image.png

image.png

对变量y添加volatile关键字,写操作和读操作加屏障,防止跳序执行

image.png

对变量x添加volatile关键字则无法实现,解决方案:

image.png

即在actor1中将x放在第二行,actor2中将x放在第一行

image.png

指令重排序的原理是什么

image.png

volatile可以保证线程安全吗

image.png

volatile和sychronized比较

image.png

什么是AQS

image.png

image.png

对于AQS,内部维护了一个state变量用于记录是否有锁,如果一个线程获取锁成功就会将state置为1,此后其他线程无法获取锁,就会进入FIFO双向队列,队列中有两个指针维护了头节点和尾节点

那如果在还无锁的条件下,有多个线程同时争抢锁呢,如何保证原子性:使用CAS

image.png

AQS既可以实现公平锁也可以实现非公平锁

image.png

image.png

非公平锁吞吐量为什么比公平锁大

image.png

ReentrantLock的实现原理

image.png

image.png

image.png

ReentrantLock是怎么实现公平锁的

image.png

image.png

image.png

Synchronized和Lock有什么区别

image.png

  • Lock提供了可打断,即某个线程正在获得锁时可以被打断,导致获取锁失败;提供了可超时,获得锁时如果超时就获取锁失败,以及可以在获得锁时使用条件等

死锁产生的条件

简单来说就类似于Spring的循环依赖

如何进行死锁诊断

image.png

image.png

image.png

ConcurrentHashMap

image.png

image.png

image.png

总结

image.png

并发程序出现问题的根本原因是什么(如何保证多线程的执行安全)

Java并发编程三大特性

原子性:

image.png

可见性:

image.png

通过volatile关键字加到共享变量flag上,就可以实现flag在线程之间可见,一个线程修改了flag,另一个线程同样会接收到flag的变化,如图一旦flag被置为了true,则线程中断

有序性:

在前面的volatile详解中已有提及,不再赘述

线程池的核心参数与执行原理

image.png

  • 关于生存时间,主要指的是救急线程(临时线程)的生存时间,一旦过期就会释放线程资源,而核心线程永远不会被释放
  • 关于阻塞队列workQueue,如果核心线程用完了,新的任务就会被放到阻塞队列,如果阻塞队列满了,就会开始使用救急线程
  • 关于拒绝策略,如果连救急线程都用完了,就会触发拒绝策略

image.png

线程池中有哪些常见的阻塞队列

image.png

image.png

强制有界是指new阻塞队列时必须传入大小,默认无界,支持有界是指new阻塞队列时可以不传大小,但会默认给你设置一个大小值

如何确定核心线程数

image.png

其中N为cpu核心数

image.png

线程池的种类有哪些

image.png

由于使用的是固定线程数的线程池(核心线程数=最大线程数目,也就是无救急线程),说明所需的线程数大体上是已知的,即使不足,也不会差别很多,因此适用于任务量已知,相对耗时的任务

image.png

单线程的线程池,只使用一个线程,可以保证任务的执行顺序

image.png

可缓存的线程池,没有核心线程,全是救急线程,救急线程的存活时间为60s,又由于全是救急线程,那就没有存在阻塞队列的必要,于是使用SynchronousQueue,任务一来,就交给救急线程执行

image.png

提交的任务可以由线程池的schedule方法指定延迟或者周期执行的时间

image.png

为什么不建议用Executor而用ThreadPoolEXecutor创建线程池

image.png

根据上一题的介绍,以上三种线程池都会出现Integer.MAX_VALUE这样的参数,肯定会有问题,所以不建议使用,而是用下面的方法

image.png

image.png

项目中哪里使用到了线程池

ES数据批量导入

image.png

等所有进程倒计时结束await才会继续执行

image.png

image.png

其实就是大批量数据分为多批次数据,由多个线程进行插入到es索引库,每执行完一个插入任务,就使count减一,直到ocunt减为0才能继续执行await

数据汇总

image.png

image.png

改串行为并行

异步线程

image.png

image.png

需要异步的方法上加注解,指定线程池

image.png

引导类启动异步调用

总结

image.png

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

image.png

对ThreadLocal的理解

image.png

image.png

其实就是保证线程池中的线程互不干扰

image.png

image.png

image.png

其实就是每个线程各自维护一个ThreadLocalMap,而ThreadLocalMap中又有一个Entry数组用于存储数据,在set时如果这个Map不存在,说明是第一次存储数据,那么就会调用ThreadLocalMap的构造方法并构造出Entry数组,然后对数据进行一些位运算,将数据存入到数组指定的位置,而get/remove的时候就同样通过确定数据的位置获取到/删除数据

image.png

image.png

ThreadLocal内存泄露问题

image.png

image.png

image.png

image.png

java里面的线程和操作系统的线程一样吗

image.png

使用多线程要注意哪些问题

image.png

调用 interrupt 是如何让线程抛出异常的

image.png

blocked和waiting有啥区别

image.png

wait 状态下的线程如何进行恢复到 running 状态

image.png

Java中有哪些常用的锁,在什么场景下使用

image.png

怎么在实践中用锁的

image.png

image.png

image.png

synchronized和reentrantlock及其应用场景

image.png

image.png

image.png

synchronized和reentrantlock区别

image.png

怎么理解可重入锁

image.png

synchronized 支持重入吗?如何实现的

image.png

syncronized锁升级的过程讲一下

image.png

image.png

JVM对Synchornized的优化

image.png

核心线程数设置为0可不可以

image.png

线程池中shutdown (),shutdownNow()这两个方法有什么作用

image.png

image.png

提交给线程池中的任务可以被撤回吗

image.png

多线程打印奇偶数,怎么控制打印的顺序

image.png