前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JAVA多线程面试题_java多线程的实现方式

JAVA多线程面试题_java多线程的实现方式

作者头像
全栈程序员站长
发布2022-09-27 15:39:13
3640
发布2022-09-27 15:39:13
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

前言

在看完《Java多线程编程核心技术》《Java并发编程的艺术》之后,对于多线程的理解到了新的境界. 先拿如下的题目试试手把.


投行面试

Q1: 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行? 答案: 使用Thread.join()方法即可.当然JUC包内提供了CountDownLatchCyclicBarrier工具类供我们选择. 如果我是面试官, 我会进行深入询问. Q: 什么是CountDownLatch?什么是CyclicBarrier?两者的区别? A: 可以重置, 接口类型不同. Q: 再深入一点,实现的机制? A: 使用锁的Condition进行完成 Q:Condition的实现机制 -> A: AQS ->CAS .刨根问底总是会将问题复杂化.

Q2: Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。 A2-1:

  • Lock相比与synchronized在使用时更加的灵活.
  • Lock的底层实现使用的是AQS -> CAS.会更加高效.
  • Lock实现了共享锁与独占锁两种机制.
  • 我们可以通过AQS自定义实现Lock.而synchronized关键字则较为难以更改.
  • 使用Lock,可以创建不同的Condition.以用于不同的唤醒工作.这是synchronizedwait/notify难以实现的.
  • 深入点: 还是Lock的实现AQS.

A2-2: 保证数据完整性与高性能缓存是两个问题.

  • 保证数据的完整性. 可以使用读锁和写锁来进行完成,比较常见的就是ReentReadWriteLock.读锁共享,写锁互斥.读读共享,写写/写读互斥.
  • 高性能缓存. 可以使用局部锁.类似ConcurrentHashMap -> Segment -> HashEntry的类型结构.反例HashTableSynchronizedMap
  • 完整性的深入在于AQS如何实现共享锁与互斥锁的.以及ReentReadWriteLock的基本实现. 我的话会将其与数据库内的读写操作进行询问.(行级锁 -> 表级锁 -> Mysql内优化 ) 高性能的深入只要掌握ConcurrentHashMap数据结构即可.

Q3: Java 中 wait 和 sleep 方法有什么区别? A: wait 与 sleep都是线程等待. 值得一提的是, wait与sleep都会使当前线程处于阻塞状态.不同点在于:

  • wait()后需要其他线程进行唤醒, sleep()后只需要等待一段时间即可;
  • wait()后会释放当前持有的锁, sleep()后不会进行释放.

Q4: 如何在 Java 中实现一个阻塞队列? A: 实现阻塞队列之前先要理解什么是阻塞队列?

  • 队列: 满足先进先出FIFO的特性即可.
  • 阻塞: 满足队列空时阻塞读线程, 队列满时阻塞写线程. 根据上述提示不难写出如下的代码(使用ReentrantLock独占锁):

class Test{ ArrayList list; volatile int count; Lock lock; Condition fullCondition; Condition emptyCondition; public Test(){ list = new CopyOnWriteArrayList(); count = 0; lock = new ReentrantLock(); fullCondition = lock.newCondition; emptyCondition = lock.newCondition; } // 弹出队列 public void offer(){ try{ lock.lock(); while(count == 0){ emptyCondition.await(); } list.get(i); count--; }finally{ lock.unlock(); } // 压入队列 public void offer(int var){ try{ lock.lock(); while(count == list.size()){ fullCondition.await(); } list.add(var); count++; }finally{ lock.unlock(); } } }

Q5: 如何在 Java 中编写代码解决生产者消费者问题? A: 生产者与消费者问题.非常类似上方的阻塞队列.这里提供一个使用LinkedBlockingQueue实现的生产者与消费者. import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class ProducerConsumerSolution { public static void main(String[] args) { BlockingQueue<Integer> sharedQ = new LinkedBlockingQueue<Integer>(); Producer p = new Producer(sharedQ); Consumer c = new Consumer(sharedQ); Consumer c2 = new Consumer(sharedQ); p.start(); c.start(); c2.start(); } } class Producer extends Thread { private BlockingQueue<Integer> sharedQueue; public Producer(BlockingQueue<Integer> aQueue) { super("PRODUCER"); this.sharedQueue = aQueue; } public void run() { // no synchronization needed for (int i = 0; i < 10; i++) { try { System.out.println(getName() + " produced " + i); sharedQueue.put(i); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { private BlockingQueue<Integer> sharedQueue; public Consumer(BlockingQueue<Integer> aQueue) { super("CONSUMER"); this.sharedQueue = aQueue; } public void run() { try { while (true) { Integer item = sharedQueue.take(); System.out.println(Thread.currentThread().getName() + " consumed " + item); } } catch (InterruptedException e) { e.printStackTrace(); } } } 因为上述的代码内直接使用了LinkedBlockingQueue是线程安全的.所以不需要更多的进行处理. Q5-2: 深入,LinkedBlockingQueue的实现原理.见上.LinkedBlockingQueue的读数据和取数据的操作都是需要加锁的. Q5-3: 是否有使用过其他的线程安全集合类?ConcurrentHashMap的读操作和写操作都需要加锁么?ConcurrentLinkedQueue呢? A5-3: ConcurrentHashMap的读操作不加锁.使用的是volatile变量.ConcurrentLinkedQueue读操作和写操作都不加锁.使用CAS进行操作.

Q6: 写一段死锁代码。你在 Java 中如何解决死锁? A6-1: 死锁发生是因为相互资源等待,而不释放自身的锁资源.举个例子 class ThreadA extends Thread{ Lock lockA; Lock lockB; public void run(){ lockA.lock(); Thread.sleep(1000); lockB.lock(); lockA.unlock(); lockB.unlock(); } } class ThreadB extends Thread{ Lock lockA; Lock lockB; public void run(){ lockB.lock(); Thread.sleep(1000); lockA.lock(); lockB.unlock(); lockA.unlock(); } } 可以看到上述的线程,

  • 线程A获取LockA后等待1s后又要获取LockB;
  • 线程B获取LockB后等待1s后又要获取LockA; 这样就会造成死锁等待现象. 死锁-操作系统的经典问题: 形成条件(1.互斥条件 2. 不可剥夺条件 3.请求与保持条件 4. 循环等待条件) 应对死锁, 通常有4种处理方法(1. 预防死锁 2. 避免死锁 3. 检测死锁 4. 解除死锁)
  • 预防死锁: 主要是破坏死锁的4个形成条件. 主要是破坏2/3/4点.
    • 对于2, 当线程无法获取到使用的资源时,即释放资源.
    • 对于3, 策略1获取所有资源后才开始运行 / 策略2 获取一定的资源开始运行.
    • 对于4, 线性运行资源.(个人感觉这样效率比较差).
  • 对于Java, 我们一般使用tryLock(long time).主要处理请求和保持条件.
  • 死锁避免 – 使用银行家算法进行调度
  • 检测死锁 – 检测是否有环路
  • 解除死锁 – 关闭所有线程 / 关闭部分线程 – 逐个终止代价最小的线程 死锁的原理以及避免算法 避免死锁的几种常见方法

Q6-2: 深入会问哲学家就餐问题银行家算法? A6-2:

  • 哲学家就餐问题:5个哲学家6只筷子. 解决措施:
  • AND策略,当获取左右2只筷子才进食.一次性获取所有的锁./
  • 记录策略 4个哲学家拿筷子,这样至少一个人可以进食.
  • 记录策略 奇偶排序, 5个人都先争取奇数筷子, 再争取偶数筷子.

Q7-1: 什么是原子操作?Java 中有哪些原子操作? A1: 原子操作是指在Java执行过程中, 要么全部成功, 要么全不成功.Java内一共提供了13种原子操作.原子操作的原理是CAS. Q7-2: 你需要同步原子操作吗? A2: 不需要同步原子操作. 原子操作是通过CAS进行控制的.CAS根据操作系统底层的不同而不同.例如Linux系统的底层脚本与Windows系统的底层脚本就不一样.

Q8: Java 中 volatile 关键字是什么?你如何使用它?它和 Java 中的同步方法有什么区别? A8: volatile关键字是将线程内的局部变量与进程内的公共变量同步.(JMM模型) 可见性 / 一致性-线程局部变量与进程变量共享 / 有序性happen-before原则, 使被volatile关键字修饰的变量不会进行重排序. Java开发中的volatile你必须要了解一下

Q9: 什么是竞态条件?你如何发现并解决竞态条件? A9: 竞态条件非常简单, 两个线程同时竞争同一个资源变量. 举个最简单的例子: class CompareThread extends Thread{ public int count; public CompareThread(int count){this.count = count;} public void run(){count++;} } 当启动两个线程的时候, count++不一定是需要的值.

  • 线程1 count=0; count+1;暂停;
  • 线程2 count=0; count+1;暂停;
  • 线程1 count=1;赋值
  • 线程2 count=1;赋值 预计输出为2, 但实际输出因为竞态为1. 解决措施: 加锁Lock / synchronized关键字 / CAS使用原子操作类 什么是竞态条件? 举个例子说明。

Q10: 在 Java 中你如何转储线程(thread dump)?如何分析它? 通过jstack -l <pid>即可. 分析: 直接阅读.或者使用相应的分析工具.

Q11: 既然 start() 方法会调用 run() 方法,为什么我们调用 start() 方法,而不直接调用 run() 方法? A11: start()方法在另启动一个子线程进行执行.run()方法不会启动子线程,而是在当前线程后顺序执行.

Q12: Java 中你如何唤醒阻塞线程? A12:

  • 如果是通过sleep()方法的阻塞,等待其时间到了即唤醒.
  • 如果是join()方法的阻塞, 当其join()的线程运行完毕后即会唤醒.
  • 如果是wait()方法的阻塞, 当其notify()的时候即会唤醒.
  • 如果是因为IO资源等问题的阻塞, 当资源获取后即会唤醒.
  • 注意: 我们有时可以使用中断, 抛出中断异常的方式让其强行唤醒.

Q13: Java 中 CyclicBarriar 和 CountdownLatch 有什么区别?

  • CountdownLatch的屏障点不可以重置, CyclicBarriar可以重置.
  • CountdownLatchawait()结束后;CyclicBarrier可以在构造函数时,指定屏障打开后的运行线程Runnable.

Q14: 什么是不可变类?它对于编写并发应用有何帮助? A: 不可变类应当是final修饰的类.无法被继承. Q14-1: 深入:String类型是不可变类. JVM的常量池.

Q15: 你在多线程环境中遇到的最多的问题是什么?你如何解决的? A15: 就个人而言, 多线程遇到最多的是资源的调优与使用. 包括数据库线程池. Spark内的每个Executor获取的资源数目. 内存干扰、竞态条件、死锁、活锁、线程饥饿是多线程和并发编程中比较有代表性的问题。这类问题无休无止,而且难于定位和调试。 这是基于经验给出的 Java 面试题。你可以看看Java 并发实战课程来了解现实生活中高性能多线程应用所面临的问题。

Q16: 线程和进程的区别? A16: 两者都是单位. 线程是操作系统的任务单位. 而线程是进程的子单位. 我们操作系统的应用通常就是一个进程.在应用内,还有许多的子线程.

Q17: 多线程的上下文切换是什么? A17: 多个线程因时间片使用完而造成的运行程序上下问直接的切换.举个例子: 线程A -> 线程B -> 线程A

Q18: 死锁和活锁的区别?死锁和饥饿的区别? A18: 活锁即我们常用的锁. 死锁是获取不到锁而是当前线程造成的死循环.死锁会造成资源的大量消耗及线程阻塞.

Q19: Java 中使用什么线程调度算法? A19: FIFO / 时间片轮转 linux进程/线程调度策略(SCHED_OTHER,SCHED_FIFO,SCHED_RR)

Q20: 线程中如何处理某个未处理异常? A20: try-catch. 设置默认异常处理器UncaughtExceptionHandler. FutureGet方法. 若无处理, 子线程会直接退出程序. Java子线程中的异常处理(通用)

Q21: 什么是线程组?为什么 Java 中不建议使用线程组? A21: ThreadGroup.

Q22: 为什么使用 Executor 框架比直接创建线程要好? A22:

  • 统一接口,管理方便.线程池的切换方便.
  • 性能高. Q22-2: 深入问题, 能讲下Executor内的基本类与基本组成么?

Q23: Java 中 Executor 和 Executors 的区别? A23: Executor接口,主要接口方法为execute();常用的是ExecutorService, 主要接口为submit()/shutdown()/isShutDown(). Executors静态类, 主要是用于创建线程池Executors.newFixedThreadPool(4).

Q24: 在 windows 和 linux 系统上分别如何找到占用 CPU 最多的线程? A24: Linux.使用top命令即可. Windows. 使用任务管理器.


面试题2

  • 什么是进程?什么是线程?

进程和线程是两个单位.进程通常是我们说的运行程序,是相对于操作系统而言的,通常可以使用ps -ef / jps进行查询得出.而线程,通常称为子线程,也就是一个进程能够分为一个或多个子线程.线程通常是为提升进程的效率而设定的.

  • 什么是多线程?

在一个进程中,我们同时开启多个线程,让多个线程去完成某些任务.(比如后台服务,就可以用多个线程响应多个客户请求.)

  • 多线程原理?

时间片轮转.

  • 线程如何启动(Java)?

实现Runnable接口和继承Thread类.

  • 线程的thread.start()thread.run()方法有什么区别?

start()方法会启动子线程,及新线程运行run()方法; run()方法,不会生成子线程(子线程)进行运行;

PS: 2019-06-12已经更正. 谢谢博友指证. 其实记也好记, run()其实直接调用Thread类中重写的run()方法, start()是新启动一个子线程, 运行run()方法.

  • synchronized关键字?
  • synchronized缺陷?x

程序阻塞.如何才会释放?效率低下.

  • 非无期限的等待? 使用Lock的优势?

Lock在一定时间内未获取,会自动进行释放; Lock在使用wait/notify的时候,可以使用不同的Condition进行控制唤醒的进程; Lock可以将读锁写锁进行分离,提升系统的运行效率.

  • 常见的线程池种类 和 基本使用?

runnablecallable.线程的回调函数.


Reference

[1] Java面试:投行的15个多线程和并发面试题 [2] 40个Java多线程问题总结

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/174555.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 投行面试
  • 面试题2
  • Reference
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档