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

JAVA多线程面试题_java多线程的实现方式前言在看完《Java多线程编程核心技术》与《Java并发编程的艺术》之后,对于多线程的理解到了新的境界.先拿如下的题目试试手把.投行面试Q1:现在有线程T1、T2和T3。你如何确保T2线程在T1之后执行,并且T3线程在T2之后执行?答案:使用Thread.join()方法即可.当然JUC包内提供了CountDownLatch与CyclicBarrier工具…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

在看完《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多线程问题总结

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • linux chmod 755的含义

    linux chmod 755的含义chmod是Linux下设置文件权限的命令,后面的数字表示不同用户或用户组的权限。一般是三个数字:第一个数字表示文件所有者的权限第二个数字表示与文件所有者同属一个用户组的其他用户的权限第三个数字表示其它用户组的权限。 权限分为三种:读(r=4),写(w=2),执行(x=1) 。 综合起来还有可读可执行(rx=5=4+1)、可读可写(rw=6=4+2)、可读可写可执行(r

  • 基于javaEE的医院病历管理系统的设计与实现[通俗易懂]

    网络的高速发展,促使着数字化医院的建设,现如今大多数医院已经在使用病历管理系统来管理患者电子病历。在医院中,病历记录了医生和患者的诊疗过程,医生可以通过之前病历记载,快速诊断患者,所以病历是医院的重要资产。使用计算机可以提高病历质量,方便存储、查阅、检索等,从而提高病案管理效率,实现病历信息同时异地共享和反复利用。电子病历的推广应用已经势不可挡,未来电子病历需求更高,应用也将继续成熟,市场的竞争也更加激烈。本次毕业设计的题目是基于javaEE的医院病历管理系统的设计与实现。本系统主要运用java编程语言、基

  • linux拷贝目录并修改名字,linux复制文件夹、重命名文件夹、删除文件夹

    linux中复制命令为cp(即copy缩写),重命名使用mv命令(即move缩写)来实现,删除命令为rm(即remove缩写)。如果操作对象是单个文件,复制和删除以及重命名很简单,如下:cpa.txtA.txt(将a.txt另存为A.txt)mva.txtA.txt(将a.txt重命名为A.txt)rma.txt(删除a.txt)linux删除和复制文件夹但是如果直接用下面…

  • LD_DEBUG使用「建议收藏」

    LD_DEBUG使用「建议收藏」LD_DEBUG1.LD_DEBUG可以用来查看程序搜索库的路径,使用方法如下:LD_DEBUG=libs./your_programLD_DEBUG=help查看命令使用方法2.执行find/usr-namelibevent-1.4.so.2得知libevnet=1.4.so.2已经安装,但是不在默认共享库的查找路径下.库路径在该目录下:/usr/local/lib/

    2022年10月10日
  • 虚拟机安装VMware Tools仍旧不能复制粘贴的解决方法–共享文件夹

    虚拟机安装VMware Tools仍旧不能复制粘贴的解决方法–共享文件夹我们有时会遇到一个问题,那就是我们可以从主机往虚拟机里复制文件,但是从虚拟机往主机复制文件就不行,鼠标永远在虚拟机内。博主重装很多次VMwareTools都没有用,这时就可以考虑共享文件夹。设置共享文件夹步骤如下:1.打开虚拟机设置,打开选项2.点击添加(A)3.设置主机路径和名称4.下一步之后文件夹共享改为总是启用(E)5.博主这里是G:\虚拟机共享文…

  • C#之CMD

    C#之CMD

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号