- 并发篇
-
- 1. 什么是线程和进程?
- 2. 程序计数器为什么是私有的?
- 3. 虚拟机栈和本地方法栈为什么是私有的?
- 4. 如何理解线程安全和不安全?
- 5. 线程的类型
- 6. 如何创建线程?
- 7. 线程的生命周期和状态
- 8. 什么是死锁,死锁产生的条件
- 9. 如何预防和避免线程死锁?
- 10. sleep() 方法和 wait() 方法对比
- 11. 如何保证变量的可见性?
- 12. 如何禁止指令重排序?
- 13. 什么是悲观锁?
- 14. 什么是乐观锁?
- 15. 如何实现乐观锁?
- 16. CAS有哪些问题
- 17. synchronized 是什么?有什么用?
- 18. synchronized 和 volatile 有什么区别?
- 19. ReentrantLock 是什么?
- 20. 公平锁和非公平锁有什么区别?
- 21. synchronized 和 ReentrantLock 有什么区别?
- 22. 可中断锁和不可中断锁有什么区别?
- 23. ThreadLocal是什么?使用场景
- 24. ThreadLocal执行流程
- 25. ThreadLocal 内存泄露问题是怎么导致的?
- 26. 为什么要用线程池?
- 27. 如何创建线程池?
- 28. 为什么不推荐使用内置线程池?
- 29. 线程池常见参数有哪些?
- 30. 线程池的饱和策略有哪些?
- 31. 线程池常用的阻塞队列有哪些?
- 32. 线程池处理任务的流程?
- 33. Future 类有什么用?
- 34. CompletableFuture 介绍 类有什么用?
- 35. AQS是什么
- 36. AQS实现原理
- 37. 基于AQS的公平锁获取锁流程
- 37. 基于AQS的非公平锁获取锁流程
- 38. AQS用到了哪些设计模式
- 39. AQS实现有哪些
并发篇
1. 什么是线程和进程?
答:
- 进程: 进程就是一个程序的实例。每个进程都拥有独立的内存空间,进程之间内存不共享。进程间的切换开销比较大。
- 线程: 线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享进程的内存空间。线程间的切换开销小。
2. 程序计数器为什么是私有的?
答:
- 程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
3. 虚拟机栈和本地方法栈为什么是私有的?
答:
- 为了保证线程中的局部变量不被别的线程访问到
4. 如何理解线程安全和不安全?
答:
- 线程安全: 不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
- 线程不安全: 多个线程同时访问时无法保证数据的一致性,正确性。
5. 线程的类型
答:
- IO 密集型:IO 密集型的线程主要进行输入输出操作,如读写文件、网络通信等,需要等待 IO 设备的响应,而不占用太多的 CPU 资源。
- CPU密集型:CPU 密集型的线程主要进行计算和逻辑处理,需要占用大量的 CPU 资源。
6. 如何创建线程?
答:
- 继承
Thread
类,重写run
方法 - 实现
Runnable
接口 - 实现
Callable
接口 (可以获得线程的返回结果) - 使用线程池
7. 线程的生命周期和状态
答:
8. 什么是死锁,死锁产生的条件
答:
- 死锁: 线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,又不释放自己资源,所以这两个线程就会互相等待而进入死锁状态。
产生死锁的四个必要条件:
- 资源互斥:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:线程在请求资源而阻塞时,不会释放已持有的资源。
- 不剥夺条件:线程持有的资源不会被其他线程抢夺。
- 循环等待条件:线程对资源的请求形成一个闭环。例如,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,以此类推,直到某个线程又开始等待线程 A 持有的资源。
9. 如何预防和避免线程死锁?
答:
破坏死锁的产生的必要条件即可:
- 破坏请求与保持条件:一次性申请所有的资源。
- 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
避免死锁就是在资源分配时,借助于算法(比如银行家算法
)对资源分配进行计算评估,使其进入安全状态。
安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3……Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 <P1、P2、P3…Pn> 序列为安全序列。
10. sleep() 方法和 wait() 方法对比
答:
共同点:两者都可以暂停当前线程的执行。
区别:
sleep()
方法没有释放锁,而wait()
方法释放了锁 。wait()
通常被用于线程间交互/通信,sleep()
通常被用于暂停执行。wait()
方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()
或者notifyAll()
方法。sleep()
方法执行完成后,线程会自动苏醒。或者也可以使用wait(long timeout)
超时后线程会自动苏醒。sleep()
是Thread
类的静态本地方法,wait()
则是Object
类的本地方法。
11. 如何保证变量的可见性?
答:
- 在 Java 中,
volatile
关键字可以保证变量的可见性。即每次使用它都到主存中进行读取。 volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。
12. 如何禁止指令重排序?
答:
指令重排序: 为了优化程序的执行效率,编译器和处理器可能会对指令做出重排序。
- 在 Java 中,
volatile
关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 - 如果我们将变量声明为
volatile
,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。
13. 什么是悲观锁?
答:
- 访问共享资源之前要先获取锁,如果获取锁成功则可以进行资源访问。如果获取失败,则进行阻塞等待锁的释放。
synchronized