拼多多Java高级开发工程师面试题(下)
由于篇幅过长,上一篇见地址:https://www.diyidaima.com/java/452.html
31、ConcurrentHashMap 的并发度是什么
ConcurrentHashMap 的并发度就是 segment 的大小,默认为 16,这意味着最多同时可以有 16 条线程操作 ConcurrentHashMap,这也是 ConcurrentHashMap 对 Hashtable 的最大优 势,任何情况下,Hashtable 能同时有两条线程获取 Hashtable 中的数据吗?
32、Linux 环境下如何查找哪个线程使用 CPU 最长
(1)获取项目的 pid,jps 或者 ps -ef | grep java
(2)top -H -p pid,顺序不能改变
33、Java 死锁以及如何避免?
Java 中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java 死锁情况出现至少两个线程和两个或更多资源。
Java 发生死锁的根本原因是:在申请锁时发生了交叉闭环申请。
34、死锁的原因
(1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。 例如:线程在获得了锁 A 并且没有释放的情况下去申请锁 B,这时,另一个线程已经获得 了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。
(2)默认的锁申请操作是阻塞的。 所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的 所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用 其它对象的延时方法和同步方法。
35、怎么唤醒一个阻塞的线程
如果线程是因为调用了 wait()、sleep()或 者 join()方法而导致的阻塞,可以中断线程,并且 通过抛出InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是 操作系统实现的,Java 代码并没有办法直接接触到操作系统。
36、不可变对象对多线程有什么帮助
前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需 要进行额外的同步手段,提升了代码执行效率。
37、什么是多线程的上下文切换
多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个就绪并 等待获取 CPU 执行权的线程的过程。
38、如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下:
(1)如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添 加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队 列,可以无限存放任务
( 2 ) 如 果 使 用 的 是 有 界 队 列 比 如 ArrayBlockingQueue , 任 务 首 先 会 被 添 加 到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程 数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒 绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
39、Java 中用到的线程调度算法是什么
抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出 一个总的优先级并分配下一个时间片给某个线程执行。
40、什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing)?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我 们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线 程等待的时间。线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选 择(也就是说不要让你的程序依赖于线程的优先级)。
41、什么是自旋
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
42、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对 比同步它有什么优势?
Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以 具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
(1)可以使锁更公平
(2)可以使线程在等待锁的时候响应中断
(3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
(4)可以在不同的范围,以不同的顺序获取和释放锁
43、单例模式的线程安全性
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境 下只会被创建一次出来。单例模式有很多种的写法,我总结一下:
(1)饿汉式单例模式的写法:线程安全
(2)懒汉式单例模式的写法:非线程安全
(3)双检锁单例模式的写法:线程安全
44、Semaphore 有什么作用
Semaphore 就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore 有一个构造 函数,可以传入一个 int 型整数 n,表示某段代码最多只有 n 个线程可以访问,如果超出 了 n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出 如果 Semaphore 构造函数中传入的 int 型整数 n=1,相当于变成了一个 synchronized 了。
45、Executors 类是什么?
Executors 为 Executor,ExecutorService,ScheduledExecutorService,ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池
46、线程类的构造方法、静态块是被哪个线程调用的
这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new 这个线程 类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么:
(1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是 Thread2 自 己调用的
(2)Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是 Thread1 自 己调用的
47、同步方法和同步块,哪个是更好的选择?
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。 请知道一条原则:同步的范围越小越好。
48、Java 线程数过多会造成什么异常?
(1)线程的生命周期开销非常高
(2)消耗过多的 CPU 资源 如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会 占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU 资源时还将产生其他 性能的开销。
(3)降低稳定性 JVM 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承 受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层 操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出 OutOfMemoryError 异常。