面试-java基础

JavaSE

面向对象特性:封装、继承、多态。多态的实现方法:继承、重写、父类引用子类对象。

集合类:继承collection接口的类和继承map接口的类。

image.png

hashmap线程不安全的原因:一:没有同步机制。二、put操作无原子性。三、两个线程同时扩容会导致链表成环形结构。四、没有可见性保证手段。

concurrenthashmap:链表超过8转换成红黑树。线程安全实现方式:JDK 1.7 采用 Segment 分段锁来保证安全, Segment 是继承自 ReentrantLock。JDK1.8 放弃了 Segment 分段锁的设计,采用 CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。

异常的分类:

Error(错误)

  • 由 JVM 抛出,表示严重问题,一般不捕获也不处理。

Exception(异常)

  • Checked Exception(受检异常):编译器强制检查,必须显式捕获或在方法签名中声明抛出。例如 IOExceptionSQLException
  • Unchecked Exception(运行时异常):继承自 RuntimeException,编译器不强制检查,可根据业务需要捕获。例如 NullPointerExceptionArithmeticException

juc(java并发编程)

特性:原子性、可用性、有序性(指令重排:会保证程序最终执行结果和代码顺序执行的结果是一致的)

线程和进程:进程是程序的一次执行过程,是系统运行程序的基本单位;一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。

•线程作为最小调度单位,进程作为资源分配的最小单位。

创建线程四种方式:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。Runnable 接口run方法无返回值;Callable接口call方法有返回值,是个泛型。

run和start的区别:start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。run(): 封装了要被线程执行的代码,可以被调用多次。调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

进程通信方式:管道、共享内存、消息队列、信号量、套接字

可中断锁和不可中断锁

  • 可中断锁:获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。ReentrantLock 就属于是可中断锁。
  • 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其他的逻辑处理。 synchronized 就属于是不可中断锁。

synchronized:依赖于JVM级别的Monitor,是悲观锁,一个线程获取到的标志就是在monitor中设置成功了Owner。

重量级锁:底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。

轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性

偏向锁:一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令

synchronized与volatile的区别

volatile只能使用在变量上;而synchronized可以在类,变量,方法和代码块上。

volatile至保证可见性;synchronized保证原子性与可见性。

volatile禁用指令重排序;synchronized不会。

volatile不会造成阻塞;synchronized

volatile:volatile关键字会强制将修改的值立即写入主存。

线程池处理任务的流程:

1,任务在提交的时候,首先判断核心线程数是否已满,如果没有满这时对于一个新提交的任务,线程池会创建一个线程去处理任务。(当线程池里面存活的线程数小于等于核心线程数corePoolSize时,线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。)

2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列,等待后续线程来执行提交地任务

3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务

4,如果当前的线程数达到了最大线程数目,并且任务队列也满了,如果还有新的任务过来,那就直接采用拒绝策略进行处理。默认的拒绝策略是抛出一个RejectedExecutionException异常。

线程池中有哪些常见的blockingqueue:ArrayBlockingQueue:基于数组实现的有界阻塞队列。LinkedBlockingQueue。SynchronousQueue:不存储元素的阻塞队列。PriorityBlockingQueue:支持优先级的无界阻塞队列。

虚拟线程:称为轻量级线程或协程,虚拟线程允许一个操作系统线程执行多个虚拟线程。提高系统的吞吐量。更适合处理大量的IO密集型任务。虚拟线程的创建方法:Thread.startVirtualThread() 创、Thread.ofVirtual() 创建 、ThreadFactory 创建。
future:Future类的主要作用是提供一种机制,允许主线程异步执行任务,并在需要时获取任务的结果。

AQS:抽象同步队列:原理是通过维护一个状态变量和一个FIFO队列,结合CAS操作,来实现线程之间的同步和互斥控制

AQS如何实现可重入:通过state。首次获取时 CAS 竞争,重入时无需 CAS,直接累加。释放时递减,直到归零真正放锁。

如何确定线程池参数:

CPU 密集型任务

  • 线程数 ≈ CPU 核数 + 1

I/O 密集型任务

  • 线程数 ≈ CPU 核数 × (1 + 阻塞系数)
  • 阻塞系数=I/O 等待时间 / 计算时间。若等待时间远大于计算时间,可配置更多线程。
  • 固定队列容量,调 corePoolSizemaximumPoolSize
    • 记录在不同线程数下的 TPS/RT 和系统资源使用情况。
    • 寻找吞吐量拐点:继续增加线程数,吞吐量不再显著提升,甚至下降的点。
  • 固定线程数,调队列容量
    • 太小会导致拒绝;太大会导致积压太深、响应延迟变高。
    • 找到拒绝率为 0 且响应延迟可接受时的最小队列容量。
  • 调整 keepAliveTime
    • 观察在低峰期线程是否能及时回收,减少资源占用。

JVM调优:

JVM 调优的参数都有哪些?

  • Xms 初始堆大小
  • Xmx 最大堆大小
  • XX:NewSize 年轻代大小
  • XX:MaxNewSize 年轻代最大值
  • XX:PermSize 永生代初始值
  • XX:MaxPermSize 永生代最大值
  • XX:NewRatio 新生代与老年代的比例

jps

输出JVM中运行的进程状态信息

jstack

查看java进程内线程的堆栈信息

jmap

用于生成堆转存快照

jstat

监视虚拟机运行时状态信息

G1:

G1的核心特点:

  1. 并行与并发:G1利用多核处理器的能力,在垃圾回收的不同阶段使用并行和并发的技术,减少对应用程序线程的影响。
  2. 分区的堆管理:G1将堆空间分割成许多相同大小的区域(Region),每个区域可以作为Eden、Survivor或Old区使用,这种布局有助于更灵活地管理内存。
  3. 垃圾优先策略:G1采用“垃圾优先”(即那些包含最少存活数据的区域(垃圾最多的区域)的策略,即根据预期的垃圾回收收益来决定哪些区域优先进行回收,从而在给定的暂停时间内获得最大的清理效果。
  4. 可预测的暂停时间:开发者可以设置期望的GC暂停时间目标,G1会尝试在不超过这个时间限制的情况下进行垃圾回收,这对于交互式应用和服务端应用非常重要。
  5. 混合回收:G1可以同时进行年轻代和老年代的垃圾回收,这有助于避免长时间的Full GC暂停。

G1和CMS区别:新老||老、可预测的停顿时间||不可、标记整理||标记清除、无内存碎片||有内存碎片。

posted @ 2025-04-22 13:03  哒令,哒哒哒哒哒~令  阅读(4)  评论(0)    收藏  举报