多线程面试题集锦

2022-04-24  本文已影响0人  Drew_MyINTYRE

Java 代码最终是被翻译成机器码执行的,机器码才是真正可以和硬件电路交互的代码。

什么是阻塞式方法?

阻塞式方法是指程序会一直等待,在该方法完成期间不做任何其他的事情,ServerSocketaccept() 方法就是一直在等待客户端的连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

Java 中堆和栈有什么不同?

每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时 volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

为什么你应该在循环中检查等待条件?

处于 等待状态的线程 可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在 notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用 wait()方法效果更好的原因。

为什么 wait 和 notify 方法要在同步块中调用?

主要是因为 Java API 强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException 异常。还有一个原因是为了避免 wait 和 notify 之间产生竞态条件。

Java 中 notify 和 notifyAll 有什么区别?

notify() 方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而 notifyAll() 会唤醒所有的线程并允许他们争夺锁,确保至少有一个等待的线程能继续运行。

Java 中用到了什么线程调度算法?

抢占式。一个线程用完 CPU 之后,操作系统会根据 线程优先级线程饥饿情况 等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0) 的作用是什么?

由于 Java 采用抢占式的线程调度算法,因此可能会出现某条线程经常能获取到 CPU 的控制权,为了让某些优先级比较低的线程也能获取到 CPU 控制权,可以使用 Thread.sleep(0) 手动触发一次操作系统分配时间片的操作,这也是平衡 CPU 控制权的一种操作。

多线程上下文切换是什么意思?

多线程的上下文切换是指 CPU 控制权 由一个已经正在运行的线程切换到另外一个就绪并等待获取 CPU 执行权的线程的过程。说白了就是 CPU 控制权交接的过程。

怎么唤醒一个阻塞的线程?

如果线程是因为调用了 wait()sleep() 或者 join() 方法而导致的阻塞,可以通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

Thread.currentThread().interrupt();

ThreadLocal 是什么?有什么应用场景?

ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。用来解决数据库连接、Session 管理等。

Jdk 中排查多线程问题用什么命令?

jstack

什么是 CAS 算法?在多线程中有哪些应用。

CAS,全称为 Compare and Swap,即比较-替换。假设有三个操作数:内存值 V、预期值 A、要修改的值 B,当且仅当预期值 A 和内存值 V 相同时,才会将内存值修改为 B 并返回 true,否则什么都不做并返回 false。当然 CAS 一定要 volatile 变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,java.util.concurrent.atomic 包下面的 Atom****类都有 CAS 算法的应用。

线程数过多会造成什么异常?

线程过多会造成栈溢出,也有可能会造成堆异常。

什么是重入锁?

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

线程 Thread.yield() 方法有什么用?

Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且 只保证当前线程放弃 CPU 占用,而不能保证使其它线程一定能占用 CPU,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。

什么是死锁?

死锁就是两个线程相互等待对方释放对象锁。

为什么要使用线程池?

当线程数达到一定数量就会耗尽系统的 CPU 和内存资源,也会造成 GC 频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

什么是活锁?

活锁是线程拿到对象锁却又相互释放不执行。当多线程中出现了相互谦让,都主动将对象锁释放给别的线程使用,这就是活锁。

什么是饥饿?

优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占资源,导致低优先级线程无法得到执行,与死锁不同的如果占用资源的线程结束了并释放了资源,那么饥饿的线程还是有机会得到执行的。

无锁

无锁,即没有对资源进行锁定,如果有多个线程修改同一个值必定会有一个线程能修改成功,而其他修改失败的线程会通过不断重试直到修改成功。 JDK 的 CAS 原理及应用即是无锁的实现。可以看出,无锁是一种非常良好的设计,它不会出现线程出现的跳跃性问题,锁使用不当肯定会出现系统性能问题,虽然无锁无法全面代替有锁,但无锁在某些场合下是非常高效的。

原子性

原子性是指一个线程的操作不能被其他线程打断,同一时间只有一个线程对一个变量进行操作。比如说多个线程同时对一个成员变量 n++ 100次,线程之间的操作是互不干扰的,这就是传说的中的原子性。但 n++ 并不是原子性的操作,要使用 AtomicInteger 保证原子性。

可见性

可见性是指某个线程修改了某一个共享变量的值,而其他线程是否可以看见该共享变量修改后的值。在单线程中肯定不会有这种问题,单线程读到的肯定都是最新的值,而在多线程编程中就不一定了。每个线程都有自己的工作内存,线程先把共享变量的值从主内存读到工作内存,形成一个副本,当计算完后再把副本的值刷回主内存,从读取到最后刷回主内存这是一个过程,当还没刷回主内存的时候这时候对其他线程是不可见的,所以其他线程从主内存读到的值是修改之前的旧值。像 CPU 的缓存优化、硬件优化、指令重排及对 JVM 编译器的优化,都会出现可见性的问题。

有序性

我们都知道程序是按代码顺序执行的,对于单线程来说确实是如此,但在多线程情况下就不是如此了。为了优化程序执行和提高 CPU 的处理性能,JVM 和操作系统都会对指令进行重排,也就说前面的代码并不一定都会在后面的代码前面执行,即后面的代码可能会插到前面的代码之前执行,只要不影响当前线程的执行结果。所以,指令重排只会保证当前线程执行结果一致,但指令重排后势必会影响多线程的执行结果。虽然重排序优化了性能,但也是会遵守一些规则的,并不能随便乱排序,只是重排序会影响多线程执行的结果。

什么是守护线程?

与守护线程相对应的就是用户线程,守护线程就是守护用户线程,当用户线程全部执行完结束之后,守护线程才会跟着结束。也就是守护线程必须伴随着用户线程,如果一个应用内只存在一个守护线程,没有用户线程,守护线程自然会退出。

上一篇 下一篇

猜你喜欢

热点阅读