【Java面试题03】Java并发编程经典面试题
文章目录
- 一、前言🚀🚀🚀
- 二、Java并发编程经典面试题:☀️☀️☀️
- 1、Java 的 synchronized 是怎么实现的?
- 2、Java 中 volatile 关键字的作用是什么?
- 3、什么是 Java 的CAS(Compare-And-Swap)操作?
- 4、说说 AQS 吧?
- 5、Java 中 ReentrantLock 的实现原理是什么?
- 6、Synchronized 和 ReentrantLock 有什么区别?
- 7、创建线程池有哪些方式?
- 8、聊一下线程池的原理
- 三、总结:🍓🍓🍓
一、前言🚀🚀🚀
☀️
你每一天的努力会在未来的某一个点交汇成宏伟的画面。
本文简介:本人是大三软件工程专业,java后端方向,学习路线:java基础->JDBC->Maven->MyBatis->SSM,通过做笔记分享到博客上的形式,激励自己学习,同时方便复习,欢迎大佬们评论或私信斧正 Thanks♪(・ω・)
二、Java并发编程经典面试题:☀️☀️☀️
1、Java 的 synchronized 是怎么实现的?
synchronized 实现原理依赖于 JVM 的 Monitor(监视器锁)和对象头(Object Header)。
当 synchronized 修饰在方法或代码块上时,会对特定的对象或类加锁,从而确保同一时刻只有一个线程能执行加锁的代码块。
● synchronized 修饰方法:会在方法的访问标志中增加一个 ACC_SYNCHRONIZED 标志。每当一个线程访问该方法时,JVM 会检查方法的访问标志。如果包含 ACC_SYICHRONIZED 标志,线程必须先获得该方法对应的对象的监视器锁(即对象锁),然后才能执行该方法,从而保证方法的同步性。
● synchronized 修饰代码块:会在代码块的前后插入 monitorenter和 monitorexit 字节码指令。可以把 monitorenter 理解为加锁,monitorexit 理解为解锁。
2、Java 中 volatile 关键字的作用是什么?
volatile 它的主要作用是保证变量的可见性和禁止指令重排优化。
1)可见性(Visibility)
● volatile 关键字确保变量的可见性。当一个线程修改了 volatile 变量的值,新值会立即被刷新到主内存中,其他线程在读取该变量时可以立即获得最新的值。这样可以避免线程间由于缓存一致性问题导致的“看见”旧值的现象。
2)禁止指令重排序(Ordering)
volatile 还通过内存屏障来禁止特定情况下的指令重排序,从而保证程序的执行顺序符合预期。对 volatile 变量的写操作会在其前面插入一个 Storestore 屏障,而对 volatile 变量的读操作则会在其后插入一个 LoadLoad 屏障。这确保了在多线程环境下,某些代码块执行顺序的可预测性。
3、什么是 Java 的CAS(Compare-And-Swap)操作?
CAS 是一种硬件级别的原子操作,它比较内存中的某个值是否为预期值,如果是,则更新为新值,否则不做修改
工作原理:
● 比较(Compare):CAS 会检查内存中的某个值是否与预期值相等
● 交换(Swap):如果相等,则将内存中的值更新为新值。
● 失败重试:如果不相等,说明有其他线程已经修改了该值,CAS 操作失败,一般会利用重试,直到成功。
4、说说 AQS 吧?
如果面试官问你为什么需要 AQS,不要长篇大论,容易把自己和面试官绕进去。 就这样简要的回答:
简单来说 AQS 就是起到了一个抽象、封装的作用,将一些排队、入队、加锁、中断等方法提供出来,便于其他相关 JUC 锁的使用,具体加锁时机、入队时机等都需要实现类自己控制。
它主要通过维护一个共享状态(state)和一个先进先出(FIFO)的等待队列,来管理线程对共享资源的访问。
state 用 volatile 修饰,表示当前资源的状态。例如,在独占锁中,state 为0表示未被占用,为1表示已被占用。
当线程尝试获取资源失败时,会被加入到 AQS 的等待队列中。这个队列是一个变体的 CLH 队列,采用双向链表结构,节点包含线程的引用、等待状态以及前驱和后继节点的指针。
AQS 常见的实现类有 ReentrantLock、countDownLatch、semaphore 等等
然后面试官会引申问你具体 ReentrantLock 的实现原理是怎样的呢?
5、Java 中 ReentrantLock 的实现原理是什么?
ReentrantLock 其实就是基于 AQS 实现的一个可重入锁,支持公平和非公平两种方式。内部实现依靠一个 state 变量和两个等待队列:同步队列和等待队列。
利用 CAS 修改 state 来争抢锁。争抢不到则入同步队列等待,同步队列是一个双向链表。条件 condition 不满足时候则入等待队列等待,是个单向链表。
是否是公平锁的区别在于:
线程获取锁时是加入到同步队列尾部还是直接利用 CAS 争抢锁。
6、Synchronized 和 ReentrantLock 有什么区别?
Synchronized 是 Java 内置的关键字,实现基本的同步机制,不支持超时,非公平,不可中断,不支持多条件。ReentrantLock 是JUC 类库提供的,由JDK1.5引入,支持设置超时时间,可以避免死锁,比较灵活,并且支持公平锁,可中断,支持多条件判断。
ReentrantLock需要手动解锁,而 Synchronized 不需要,它们都是可重入锁。一般情况下用 Synchronized 足矣,比较简单,而 ReentrantLock 比较灵活,支持的功能比较多,所以复杂的情况用 ReentrantLock。
性能问题:很多年前,Synchronized 性能不如 ReentrantLock,现在基本上性能是一致的。
7、创建线程池有哪些方式?
1)使用 Executors 工厂类,例如 Executors.newFixedThreadPool(10);
2)使用 ThreadPoolExecutor 直接创建线程池
ExecutorService threadPool = new ThreadPoolExecutor(5, // corePoolSize10, // maximumPoolSize60, // keepAliveTimeTimeUnit.SECONDS, // TimeUnitnew LinkedBlockingQueue<Runnable>(100) // BlockingQueue
);
3)通过 ForkJoinPool 创建并行任务线程池。
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(() -> {// Task
});
8、聊一下线程池的原理
线程池是一种池化技术,用于预先创建并管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度。
它几个关键的配置包括:核心线程数、最大线程数、空闲存活时间、工作队列、拒绝策略。
主要工作原理如下:
- 默认情况下线程不会预创建,任务提交之后才会创建线程(不过设置 prestartAllCoreThreads 可以预创建核心线程)
- 当核心线程满了之后不会新建线程,而是把任务堆积到工作队列中。
- 如果工作队列放不下了,然后才会新增线程,直至达到最大线程数。
- 如果工作队列满了,然后也已经达到最大线程数了,这时候来任务会执行拒绝策略。
- 如果线程空闲时间超过空闲存活时间,并且当前线程数大于核心线程数的则会销毁线程,直到线程数等于核心线程数(设置allowCoreThreadTimeOut 为 true 可以回收核心线
三、总结:🍓🍓🍓
以上就是今天要讲的内容。