程序员必备知识-------多线程面试问题集锦

在这里插入图片描述

一、如何停止一个线程

  • 使用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;

六、如何确保线程安全?

在Java中可以有很多方法来保证线程安全,诸如:

  • 通过加锁(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变量对应的值。

(1). ThreadLocal可能引起的内存泄露

下面是ThreadLocalMap的部分源码,我们可以看出ThreadLocalMap里面对Key的引用是弱引用。那么,就存在这样的情况:当释放掉对threadlocal对象的强引用后,map里面的value没有被回收,但却永远不会被访问到了,因此ThreadLocal存在着内存泄露问题。

static class ThreadLocalMap {
 /**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object). Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table. Such entries are referred to
 * as "stale entries" in the code that follows.
 */
 static class Entry extends WeakReference<ThreadLocal> {
 /** The value associated with this ThreadLocal. */
 Object value;
 Entry(ThreadLocal k, Object v) {
 super(k);
 value = v;
 }
 }
 ...
 }

看下面的图示, 实线代表强引用,虚线代表弱引用。每个thread中都存在一个map,map的类型是上文提到的ThreadLocal.ThreadLocalMap,该map中的key为一个ThreadLocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向ThreadLocal对象。一旦把threadlocal实例置为null以后,那么将没有任何强引用指向ThreadLocal对象,因此ThreadLocal对象将会被 Java GC 回收。但是,与之关联的value却不能回收,因为存在一条从current thread连接过来的强引用。 只有当前thread结束以后, current thread就不会存在栈中,强引用断开,Current Thread、Map及value将全部被Java GC回收。
在这里插入图片描述

所以,得出一个结论就是:只要这个线程对象被Java GC回收,就不会出现内存泄露。但是如果只把ThreadLocal引用指向null而线程对象依然存在,那么此时Value是不会被回收的,这就发生了我们认为的内存泄露。比如,在使用线程池的时候,线程结束是不会销毁的而是会再次使用的,这种情形下就可能出现ThreadLocal内存泄露。

Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal进行get、set操作时会清除线程Map里所有key为null的value。所以最怕的情况就是,ThreadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,线程结束后被放回线程池中而不销毁,那么如果这个线程一直不被使用或者分配使用了又不再调用get/set方法,那么这个期间就会发生真正的内存泄露。因此,最好的做法是:在不使用该ThreadLocal对象时,及时调用该对象的remove方法去移除ThreadLocal.ThreadLocalMap中的对应Entry

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

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

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

(1). Jconsole:JDK自带的图形化界面工具,主要用于对 Java 应用程序做性能分析和调优。
在这里插入图片描述
(2). Jstack:JDK自带的命令行工具,主要用于线程Dump分析。
在这里插入图片描述
(3). VisualVM:JDK自带的图形化界面工具,主要用于对 Java 应用程序做性能分析和调优。
在这里插入图片描述

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

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

Timer timer = new Timer();
timer.schedule(new TimerTask() {
 public void run() {
 System.out.println("abc");
 }
}, 200000 , 1000);

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

十二、CAS : CAS自旋volatile变量,是一种很经典的用法。

十三、AQS : 队列同步器

十四、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?

十五、Condition

十六、什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

十七、同步容器(强一致性)

十八、什么是CopyOnWrite容器(弱一致性)?

十九、ConcurrentHashMap (弱一致性)

二十、happens-before

二十一、锁优化技术

二十二、主线程等待子线程运行完毕再运行的方法

最后

由于篇幅过长的原因,为了不影响大家的阅读效果,文中没有给到所有的答案。我这里以文件的形式整理好了,需要借阅的程序员朋友可以加java架构进阶交流群(962697803)免费来领取。点击我,加入

收集了还有你不知道的其它面试题(springboot、mybatis、并发、java中高级面试总结等)
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiaoze_JAVA/article/details/88825262