java多线程、并发面试题

本文深入探讨Java并发编程的关键概念和技术,包括CyclicBarrier与CountDownLatch的区别、volatile变量的作用、Java内存模型的理解、异常处理机制、ThreadLocal及FutureTask的使用、线程池的工作原理及其类型选择等。
  1. Java中CyclicBarrier 和 CountDownLatch有什么不同?
    CountDownLatch 的计数器只能使用一次,而CyclicBarrier 的计数器可以使用reset() 方法重置,所以后者能处理更为复杂的业务场景;CountDownLatch 是一个(或多个)线程等待另外N个执行任务的线程,而CyclicBarrier 是N个线程互相等待,任何一个线程完成之前,所有线程都必须等待;CyclicBarrier 的实现原理是,利用ReentrantLock 做线程安全锁,实现线程安全等待。
  2. Java中的volatile 变量是什么?
    java语言规范对volatile 的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排它锁单独获得这个变量。
    volatile 变量修饰的共享变量进行写操作时,汇编代码会多出一个Lock 前缀的指令,这个指令会引发两件事情:

    • 将当前处理器缓存行的数据写回到系统内存。

    • 这个写回内存的操作会使在其他CPU 里缓存了该内存地址的数据无效。

两个特性:内存可见性和禁止指令重排序。
可见性:当写一个volatile 变量时,JVM会把该线程对应的本地内存中的共享变量值刷新到主内存;而读一个volatile 变量时,JVM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。
禁止重排序: 编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
3. Java内存模型是什么?
Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保了:

  • 线程内的代码能够按先后顺序执行,这被称为程序次序规则。
  • 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
  • 前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
  • 一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
  • 一个线程的所有操作都会在线程终止之前,线程终止规则。
  • 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
  • 可传递性

4、 一个线程运行时发生异常会怎样?
多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常,异常会被直接抛出到控制台(由于线程的本质,使得你不能捕获从线程中逃逸的异常。一旦异常逃逸出任务的run方法,它就会向外传播到控制台,除非你采用特殊的形式捕获这种异常。),这样会让你很头疼,无法捕捉到异常就无法处理异常而引发的问题。
简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handleruncaughtException()方法进行处理。
总结:在java中要捕捉多线程产生的异常,需要自定义异常处理器,并设定到对应的线程工厂中。
5. 什么是ThreadLocal变量?
ThreadLocal ,即线程变量,是一个以ThreadLocal 对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal 对象查询到绑定在这个线程上的一个值。
ThreadLocal 实现线程隔离的秘密,在于ThreadLocalMap 类。它是ThreadLocal 的一个静态内部类,实现了键值对的设置和获取,每个线程都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。
我们可以创建不同的ThreadLocal 实例来实现多个变量在不同线程间的访问隔离(因为不同的ThreadLocal 对象作为不同键,就像你在Map 中设置多个键值对一样)。
ThreadLocal 在处理线程的局部变量的时候比synchronized 更简单,更方便,且结果程序拥有更高的并发性。
使用ThreadLocal ,一直都是声明在静态变量中,如果不断地创建ThreadLocal 而且没有调用其remove() 方法,将会导致内存泄漏,特别是在高并发的Web容器中。
6. 什么是FutureTask?
FeatureTask 作为Feature 接口的唯一实现类,代表异步计算的结果,同时也实现了Runnable 接口,因此,FeatureTask 可以交给Executor 执行,也可以由调用线程直接执行(FeatureTask.run())。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。
包括:未启动、已启动、已完成三种状态。
7. Java中interrupted 和 isInterruptedd方法的区别?
interrupted()isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
8. 为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。
9. 线程池的种类,区别和使用场景
线程池: 创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
分类:
newFixedThreadPool 将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化(如果某个线程由于发生了未预期的Exception 而结束,那么线程池将会补充一个新的线程)。
newCachedThreadPool 将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何的限制。
newSingleThreadExecutor 是一个单线程的Executor ,它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建一个线程来替代他。它能确保依照任务在队列中的顺序来串行执行。
newScheduledThreadPool 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer
好处:
(1)降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
(2)提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提供线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调控和优化。但是要做到合理地利用线程池,必须对其原理深入了解。
(4)防止服务器过载,形成内存溢出,或者CPU耗尽。
如何提高服务器性能:多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
应用范围:需要大量的线程来完成任务,且完成任务的时间比较短;对性能要求苛刻的应用;接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
工作机制及其原理:

    线程池的核心的两个队列:
    **线程等待池:** 即线程队列`BlockingQueue`;
    **任务处理池:(`PoolWorker`)**,即正在工作的`Thread` 列表(`HashSet<Worker>`)。
    线程池的核心的参数:
    **核心池大小(`corePoolSize`):**,即固定大小,设定好之后,线程池的稳定峰值,达到这个值之后池的线程数大小不会释放。
    **最大处理线程池数(maximumPoolSize):**,当线程池里面的线程数超过`corePoolSize`,小于`maximumPoolSize` 时会动态地创建与回收线程池里面的线程的的资源。

实现原理:
当向线程池提交一个任务之后,处理流程:
(1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务;如果是,则进入下个流程。
(2)线程池判断工作队列是否已满。如果没有满,则将新提交的任务存储在这个工作队列里;如果满了,进入下个流程。
(3)线程池判断线程池中的线程是否都处于工作状态。如果不是,则创建一个新的工作线程来执行任务;如果是,则交给饱和策略来处理这个任务。
工作队列类
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
饱和策略:
AbortPolicy: 直接抛出异常。
CallerRunsPolicy: 只用调用者所在线程来运行任务。
DiscardOldestPolicy: 丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy: 不处理,直接丢弃掉。
10. 用Java写代码来解决生产者——消费者问题。
待补充…………………
11. 用Java编程一个会导致死锁的程序,你将怎么解决?
待补充…………
12. 什么是竞争条件?你怎样发现和解决竞争?
13. 你将如何使用thread dump?你将如何分析Thread dump?
14. Java中你怎样唤醒一个阻塞的线程?
15. 什么是不可变对象,它对写并发应用有什么帮助?
16. 你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
17. 死锁与活锁的区别,死锁与饥饿的区别?
18. Java中用到的线程调度算法是什么?
19. 在Java中什么是线程调度?
20. 为什么使用Executor框架比使用应用创建和管理线程好?
21. 在Java中Executor和Executors的区别?
22. 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步一点_点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值