多线程—可能你会遇到的多线程面试题都在这里了(含答案)

找工作告一段落,期间经历了很多事情,也思考了许多问题,最后也收获了一些沉甸甸的东西 —— 成长和一些来自阿里、百度、京东(sp)、华为等厂的Offer。好在一切又回到正轨,接下来要好好总结一番才不枉这段经历,遂将此过程中笔者的一些笔试/面试心得、干货发表出来,与众共享之
多线程—可能你会遇到的多线程面试题都在这里了(含答案)

**本文对面试/笔试过程中经常会被问到的一些关于并发编程的问题进行了梳理和总结,包括线程池、并发控制锁、并发容器和队列同步器等基础知识点,**一方面方便自己温故知新,另一方面也希望为找工作的同学们提供一个复习参考

面试题(答案仅供参考哦)

如何停止一个线程

  • 使用volatile变量终止正常运行的线程 + 抛异常法/Return法
  • 组合使用interrupt方法与interruptted/isinterrupted方法终止正在运行的线程 + 抛异常法/Return法
  • 使用interrupt方法终止 正在阻塞中的 线程

何为线程安全的类?

在线程安全性的定义中,最核心的概念就是 正确性。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?

Object lock = new Object();
synchronized (lock) {
	lock.wait();
	...
}

Wait-notify机制是在获取对象锁的前提下不同线程间的通信机制。在Java中,任意对象都可以当作锁来使用,由于锁对象的任意性,所以这些通信方法需要被定义在Object类里。

为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?

wait/notify机制是依赖于Java中Synchronized同步机制的,其目的在于确保等待线程从Wait()返回时能够感知通知线程对共享变量所作出的修改。如果不在同步范围内使用,就会抛出java.lang.IllegalMonitorStateException的异常。

并发三准则

  • 异常不会导致死锁现象:当线程出现异常且没有捕获处理时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象,同时还会释放CPU;
  • 锁的是对象而非引用;
  • 有wait必有notify;

如何确保线程安全?

  • 通过加锁(Lock/Synchronized)保证对临界资源的同步互斥访问;
  • 使用volatile关键字,轻量级同步机制,但不保证原子性;
  • 使用不变类 和 线程安全类(原子类,并发容器,同步容器等)。

volatile关键字在Java中有什么作用

volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新,即保证了内存的可见性,除此之外还能 禁止指令重排序。此外,synchronized关键字也可以保证内存可见性。

指令重排序问题在并发环境下会导致线程安全问题,volatile关键字通过禁止指令重排序来避免这一问题。而对于Synchronized关键字,其所控制范围内的程序在执行时独占的,指令重排序问题不会对其产生任何影响,因此无论如何,其都可以保证最终的正确性。

ThreadLocal及其引发的内存泄露

ThreadLocal是Java中的一种线程绑定机制,可以为每一个使用该变量的线程都提供一个变量值的副本,并且每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本发生冲突。

每个线程内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals,这个 threadLocals 存储了与该线程相关的所有 ThreadLocal 变量及其对应的值,也就是说,ThreadLocal 变量及其对应的值就是该Map中的一个 Entry,更直白地,threadLocals中每个Entry的Key是ThreadLocal 变量本身,而Value是该ThreadLocal变量对应的值。

什么是死锁(Deadlock)?如何分析和避免死锁?

死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。

分析死锁,我们需要查看Java应用程序的线程转储。我们需要找出那些状态为BLOCKED的线程和他们等待的资源。每个资源都有一个唯一的id,用这个id我们可以找出哪些线程已经拥有了它的对象锁

什么是Java Timer类?如何创建一个有特定时间间隔的任务?

Timer是一个调度器,可以用于安排一个任务在未来的某个特定时间执行或周期性执行。TimerTask是一个实现了Runnable接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用Timer去安排它的执行

什么是线程池?如何创建一个Java线程池?

一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。线程池可以避免线程的频繁创建与销毁,降低资源的消耗,提高系统的反应速度。java.util.concurrent.Executors提供了几个java.util.concurrent.Executor接口的实现用于创建线程池,其主要涉及四个角色:

  • 线程池:Executor
  • 工作线程:Worker线程,Worker的run()方法执行Job的run()方法
  • 任务Job:Runable和Callable
  • 阻塞队列:BlockingQueue

锁优化技术

锁优化技术的目的在于线程之间更高效的共享数据,解决竞争问题,更好提高程序执行效率。

  • 自旋锁(上下文切换代价大):互斥锁 -> 阻塞 –> 释放CPU,线程上下文切换代价较大 + 共享变量的锁定时间较短 == 让线程通过自旋等一会儿,自旋锁
  • 锁粗化(一个大锁优于若干小锁):一系列连续操作对同一对象的反复频繁加锁/解锁会导致不必要的性能损耗,建议粗化锁
  • 一般而言,同步范围越小越好,这样便于其他线程尽快拿到锁,但仍然存在特例。
  • 偏向锁(有锁但当前情形不存在竞争):消除数据在无竞争情况下的同步原语,提高带有同步但无竞争的程序性能。
  • 锁消除(有锁但不存在竞争,锁多余):JVM编译优化,将不存在数据竞争的锁消除

本文到此结束,喜欢的朋友帮忙点点赞和关注,感谢!!

猜你喜欢

转载自blog.csdn.net/qwe123147369/article/details/91522383