基础篇
- 前言
- 一、线程
- 二、JUC包
- 三、CAS和AQS
- 四、线程池
- 四、jvm GC
- 五、锁
- 五、淘汰策略
前言
博主也非常不想看一些有用没有的八股文,但是大部分公司面试这个是必须的,不然面试官可能都不知道拿什么区判定你的编程功底。so~,我整理了一部分还算有用的,自用也是分享。
一、线程
- 定义一个继承Thread类的子类,重写run()方法,编写线程执行的逻辑,创建子类实例,调用其start()方法启动线程(而非直接调用run()方法)。简单直观,但Java是单继承语言,继承Thread类会占用类的继承权。
- 定义一个实现Runnable接口的类,实现run()方法,定义线程逻辑,创建该类的实例,并将其作为参数传递给Thread类的构造函数,调用Thread对象的start()方法启动线程。更灵活,允许类继承其他父类。
- 使用Callable和Future,定义一个实现Callable接口的类(泛型指定返回值类型)。实现call()方法,编写线程逻辑并返回结果。创建FutureTask对象包装Callable实例。将FutureTask作为参数创建Thread对象,并启动线程。通过FutureTask.get()获取线程执行结果。支持返回值,且能捕获线程执行中的异常。
- 通过Executors工厂类创建线程池(如newFixedThreadPool、newCachedThreadPool等)。提交任务到线程池(支持Runnable或Callable任务)。关闭线程池(调用shutdown()或shutdownNow())。减少线程创建和销毁的开销,适合高并发场景。
二、JUC包
java.util.concurrent,这是Java并发编程的核心工具包。
介绍:JUC主要分为atomic、locks和concurrent包下的其他类。atomic和locks包,分别使用CAS和AQS技术。Atomic包提供原子操作类,比如AtomicInteger,而locks包提供了显式锁,如ReentrantLock。原子类解决单个变量的线程安全问题,而锁机制比synchronized更灵活。concurrent下还有线程池、并发容器和同步工具等。
JUC主要包含以下模块:
-
原子类(Atomic包):
基于CAS(Compare-and-Swap)机制,提供线程安全的原子操作类,如AtomicInteger、AtomicLong等,适用于单个变量的线程安全场景。原子类分为基本类型、数组类型、引用类型、字段更新类型和累加器(如LongAdder)。 -
锁机制(Locks包):
提供显式锁(如ReentrantLock、ReadWriteLock),基于AQS(AbstractQueuedSynchronizer)实现,支持更灵活的锁操作(如尝试获取锁、超时机制),优于传统的synchronized关键字。 -
并发容器:
包括线程安全的集合类,如ConcurrentHashMap(分段锁实现)、CopyOnWriteArrayList(写时复制),以及阻塞队列(如ArrayBlockingQueue、LinkedBlockingQueue),用于高效的多线程数据共享。 -
线程池(Executor框架):
通过ExecutorService、ThreadPoolExecutor等类管理线程生命周期,减少线程创建/销毁开销,支持任务调度和异步执行。 -
同步工具类:
如CountDownLatch(闭锁,等待多线程完成)、CyclicBarrier(栅栏,线程同步等待)、Semaphore(信号量,控制资源访问),用于协调线程间的协作。
三、CAS和AQS
-
AQS(AbstractQueuedSynchronizer)
AQS 是Java并发包(java.util.concurrent)的底层框架,用于构建锁和同步器(如 ReentrantLock、Semaphore)。
其核心思想是:
同步状态(state) :通过 volatile int state 表示共享资源的状态(如锁是否被占用)。
CLH队列:通过双向队列(FIFO)管理等待线程,避免竞争。
核心方法:
独占模式(如 ReentrantLock):
acquire(int arg):获取资源(如加锁)。
release(int arg):释放资源(如解锁)。
共享模式(如 Semaphore):
acquireShared(int arg):获取共享资源。
releaseShared(int arg):释放共享资源。
开发者需实现的关键方法:
tryAcquire(int):尝试获取独占资源。
tryRelease(int):尝试释放独占资源。
tryAcquireShared(int):尝试获取共享资源。
tryReleaseShared(int):尝试释放共享资源。
特点:
模板方法模式:AQS定义算法骨架,子类实现具体逻辑。
支持公平/非公平锁:通过队列管理实现公平性。
线程阻塞与唤醒:通过 LockSupport.park() 和 LockSupport.unpark() 控制线程状态。 -
CAS(Compare-And-Swap)
CAS 是一种无锁(Lock-Free)的原子操作,用于实现多线程环境下的变量更新。
其核心操作基于以下三个参数:
内存地址(V) :需要更新的变量地址。
预期原值(A) :线程认为变量当前的值。
新值(B) :希望将变量更新为的值。
操作逻辑:只有当 V 的值等于 A 时,才会将 V 的值修改为 B。整个过程是原子的,由底层硬件(如CPU的CMPXCHG指令)保证。
优点:
无锁化:避免线程阻塞和上下文切换,性能高。
轻量级:适用于低竞争场景(如计数器、状态标志)。
缺点:
ABA问题:变量可能被其他线程从A改为B再改回A,CAS无法感知中间变化。
解决方案:使用 AtomicStampedReference 添加版本号。
自旋开销:高竞争下CAS失败重试可能导致CPU空转。
仅支持单一变量:无法保证多个变量的原子性操作。
CAS:提供无锁化的原子操作,是构建高性能并发工具的基础。
AQS:通过队列和状态管理,为复杂同步工具提供标准化实现框架。
两者共同构成了Java并发编程的核心,CAS负责微观的原子操作,AQS负责宏观的线程同步。
四、线程池
- 线程池类型
FixedThreadPool适合稳定的并发负载。
CachedThreadPool适合处理大量短期异步任务。
ScheduledThreadPool用于定时任务。
SingleThreadExecutor用于需要顺序执行任务的场景。 - 线程池参数
常见的核心参数包括corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory和handler。
corePoolSize(核心线程数)
线程池中保持的最小线程数,即使空闲也不会被销毁。初始化时线程数为0,任务提交后逐步创建,直到达到corePoolSize。
作用:保证系统有基本线程处理任务,避免频繁创建线程的开销。
默认行为:可通过配置allowCoreThreadTimeOut(true)让核心线程超时回收。
maximumPoolSize(最大线程数)
线程池允许创建的最大线程数,包括核心线程和非核心线程。当任务队列已满且当前线程数小于此值时,会创建新线程处理任务。
作用:应对突发任务高峰,防止任务堆积。
keepAliveTime(线程空闲存活时间)
非核心线程(或允许回收的核心线程)在空闲时的存活时间,超时后会被销毁。
unit(单位)
通过unit参数指定(如秒、毫秒)。
作用:避免资源浪费,动态调整线程数量。
workQueue(任务队列)
用于缓存等待执行的任务,必须是阻塞队列(BlockingQueue)。
常见类型:
有界队列(如ArrayBlockingQueue):防止任务无限堆积,但可能导致任务被拒绝。
无界队列(如LinkedBlockingQueue):可能导致内存溢出,但保证任务不被拒绝。
同步移交队列(如SynchronousQueue):不存储任务,直接创建新线程处理,适用于高吞吐场景。
threadFactory(线程工厂)
自定义线程的创建方式,可设置线程名称、优先级、守护线程等,便于监控和调试。
默认实现:Executors.defaultThreadFactory()。
handler(拒绝策略)
当线程池和队列均满时,处理新提交任务的策略。JDK内置四种策略:
AbortPolicy(默认) :抛出RejectedExecutionException异常。
CallerRunsPolicy:由提交任务的线程直接执行任务。
DiscardOldestPolicy:丢弃队列中最旧的任务,重新提交新任务。
DiscardPolicy:静默丢弃新任务,不抛异常。
自定义策略:实现RejectedExecutionHandler接口,如记录日志或降级处理。
四、jvm GC
首先,调优的原则方面,多数Java应用不需要GC调优,问题多出在代码而非参数。(核心原则是优先优化代码,再调整JVM参数)
Minor GC: 针对新生代(Young Generation)的回收,当Eden区满时触发,存活对象会被复制到Survivor区或晋升到老年代(Old Generation)
Full GC: 对整个堆(新生代、老年代、元空间/永久代)进行回收,触发条件包括老年代空间不足、显式调用System.gc()等,应尽量减少其频率
算法: 标记-清除、标记-整理、复制算法,不同回收器组合使用这些算法
调优前提: 当GC时间过长(如Minor GC >50ms,Full GC >1秒)或频率过高(如Full GC间隔<10分钟)时需优化
监控与分析工具:
!jstat -gc :实时查看GC次数、时间及内存分布
!jmap生成堆转储文件,结合MAT分析内存泄漏
!GC日志:通过-Xloggc: -XX:+PrintGCDetails输出日志,使用GCViewer或GCeasy可视化分析
部分参数:
通用:-Xms4g -Xmx4g # 堆内存固定4G-XX:NewRatio=2 # 新生代:老年代=1:2-XX:SurvivorRatio=8 # Eden:Survivor=8:1:1-XX:+UseG1GC # 启用G1回收器-XX:MaxGCPauseMillis=200 # 目标最大停顿时间-XX:+PrintGCDetails -Xloggc:/path/to/gc.log # 输出详细GC日志
G1:-XX:InitiatingHeapOccupancyPercent=45 # 堆占用45%时启动并发GC-XX:G1HeapWastePercent=5 # 允许5%的堆空间浪费
CMS:-XX:+UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction=75 # 老年代75%时触发CMS-XX:+UseCMSCompactAtFullCollection # Full GC后压缩内存
五、锁
-
synchronized
通过字节码指令 monitorenter 和 monitorenterexit 实现。
可修饰方法(类锁/实例锁)或代码块(指定锁对象)。 -
ReentrantLock
支持公平/非公平模式,提供 tryLock()、lockInterruptibly() 等灵活方法。
需手动释放锁,建议在 finally 中调用 unlock()。 -
ReadWriteLock
读写分离:写锁独占,读锁共享(如 ReentrantReadWriteLock)。
适用场景:如缓存系统(读多写少)。 -
Semaphore
控制并发线程数(如数据库连接池)。
五、淘汰策略
LRU(Least Recently Used) : 淘汰最久未访问的数据,适合热点数据场景。
LFU(Least Frequently Used) : 淘汰使用频率最低的数据,适合长周期缓存。
FIFO(First In First Out) : 按写入顺序淘汰,适合时效性强的数据。