ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

java面试基础知识(4)

2026/8/20 13:03:50 拓冰建站 浏览量
java面试基础知识(4) 第三章 Java并发编程⚠️ 并发编程是中厂面试问深度的地方必须理解底层原理不能只背结论。3.1 线程基础3.1.1 线程的创建方式// 方式1继承 Thread 类classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(Thread running: Thread.currentThread().getName());}}newMyThread().start();// ⚠️ 调用 start() 而非 run()// 方式2实现 Runnable 接口推荐可继承其他类classMyRunnableimplementsRunnable{Overridepublicvoidrun(){System.out.println(Runnable running);}}newThread(newMyRunnable()).start();// 方式3实现 Callable 接口有返回值可抛异常classMyCallableimplementsCallableString{OverridepublicStringcall()throwsException{Thread.sleep(1000);returntask done!;}}FutureTaskStringfutureTasknewFutureTask(newMyCallable());newThread(futureTask).start();StringresultfutureTask.get();// 阻塞直到获取结果// 方式4线程池生产环境唯一推荐方式ExecutorServiceexecutorExecutors.newFixedThreadPool(3);FutureStringfutureexecutor.submit(newMyCallable());Stringresultfuture.get();3.1.2 线程的生命周期6种状态// Thread.State 枚举定义的6种状态// NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED// NEW创建但未 start()// RUNNABLE可运行包括正在运行和等待CPU调度// BLOCKED等待获取synchronized锁// WAITING无限期等待wait/join/park不带超时// TIMED_WAITING有限等待sleep/wait(timeout)/join(timeout)// TERMINATED执行完毕// start() 和 run() 的区别// start()启动新线程JVM调用run()线程进入RUNNABLE状态// run()只是一个普通方法调用在当前线程执行不会创建新线程3.2 synchronized 关键字⚠️ 锁升级过程是近年面试绝对重点从偏向锁到重量级锁的升级路径必须能画出来并解释每一步。3.2.1 锁升级过程JDK 6 的锁优化synchronized 在 JDK 6 之后进行了重大优化引入了锁升级机制不再是直接使用重量级锁无锁 → 偏向锁 → 轻量级锁自旋锁→ 重量级锁不可逆/* * 1. 偏向锁Biased Locking * - 场景大多数情况下锁总是被同一个线程获取没有竞争 * - 原理在 Mark Word 中记录偏向线程ID * - 获取只需检查 Mark Word 的偏向线程ID是否是自己 * - 撤销当其他线程竞争时偏向锁撤销升级为轻量级锁 * - JVM参数-XX:UseBiasedLockingJDK6-14默认开启JDK15默认关闭 * - 延迟偏向锁在JVM启动4秒后才生效-XX:BiasedLockingStartupDelay0关闭 * * 2. 轻量级锁Lightweight Locking * - 场景多个线程交替执行无实际竞争 * - 原理CAS操作 Mark Word将锁记录指针指向线程栈中的 Lock Record * - 自旋获取失败时自旋重试自适应自旋JDK6根据历史决定自旋次数 * * 3. 重量级锁Heavyweight Locking * - 场景存在实际竞争自旋超时/竞争激烈 * - 原理通过 monitor 对象底层使用操作系统的互斥量mutex * - 代价涉及用户态→内核态的切换性能开销大 */// Mark Word 结构64位JVM可压缩// |--------------------------------------------------------------|// | Mark Word (64 bits 8 bytes) |// |--------------------------------------------------------------|// | 锁状态 | 25bit | 31bit | 1bit | 4bit | 2bit |// | 无锁 | unused | hashCode | unused| age | 01 |// | 偏向锁 | thread(54bit) | epoch(2bit)| unused| age | 01 |// | 轻量锁 | ptr_to_lock_record(62bit) | 00 |// | 重量锁 | ptr_to_monitor(62bit) | 10 |// | GC标记 | | 11 |3.2.2 类锁 vs 实例锁classSyncDemo{// 实例锁锁的是当前实例对象 thispublicsynchronizedvoidmethod1(){}// 等价于 synchronized(this){}// 类锁锁的是 Class 对象 SyncDemo.classpublicstaticsynchronizedvoidmethod2(){}// 等价于 synchronized(SyncDemo.class){}// 实例锁和类锁互不影响不同的锁对象}// 使用注意事项SyncDemoobj1newSyncDemo();SyncDemoobj2newSyncDemo();// obj1.method1() 和 obj2.method1() 可以并发执行 —— 不同实例不同锁// obj1.method1() 和 SyncDemo.method2() 可以并发执行 —— 实例锁和类锁是不同的锁3.2.3 synchronized vs ReentrantLock3.3 volatile 关键字volatile 是 Java 中最轻量级的同步机制保证变量的可见性和禁止指令重排但不保证原子性。3.3.1 可见性每个线程有自己的工作内存CPU缓存volatile 保证写操作立即刷新到主内存读操作从主内存获取即所有线程都能看到最新值。// 可见性示例标志位退出classVolatileDemo{privatevolatilebooleanrunningtrue;// 去掉volatile可能永远不退出publicvoidstop(){runningfalse;}publicvoidrun(){while(running){// 如果不加volatile线程可能一直读自己的缓存看不到stop()的修改}}}3.3.2 禁止指令重排编译器和CPU可能对指令进行重排序优化。volatile 通过内存屏障Memory Barrier禁止特定类型的重排序。// 经典场景单例模式的双重检查锁DCLpublicclassSingleton{// ⚠️ 必须加 volatile否则可能拿到半初始化对象privatevolatilestaticSingletoninstance;publicstaticSingletongetInstance(){if(instancenull){// 第一次检查synchronized(Singleton.class){if(instancenull){// 第二次检查instancenewSingleton();// 这行代码不是原子操作// 实际上分为3步// ① 分配内存空间// ② 初始化对象调用构造方法// ③ instance 指向内存地址// 不加volatile②和③可能重排导致其他线程拿到未初始化完成的对象}}}returninstance;}}// 现代优雅写法静态内部类利用类加载的线程安全特性publicclassSingleton{privateSingleton(){}privatestaticclassHolder{privatestaticfinalSingletonINSTANCEnewSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;// 类加载时初始化天然线程安全}}3.4 CAS 与 ABA 问题3.4.1 CAS 原理CASCompare And Swap比较并交换是一条 CPU 原子指令cmpxchg用于实现无锁并发。它包含三个操作数内存地址 V、预期原值 A、新值 B。如果 V 的值等于 A则将 V 更新为 B否则不更新。// JDK中的 CAS 实现// Unsafe类提供 native CAS 操作// AtomicInteger 就是基于 CAS 实现的publicclassAtomicInteger{privatevolatileintvalue;privatestaticfinalUnsafeunsafeUnsafe.getUnsafe();publicfinalintgetAndIncrement(){// 自旋 CASreturnunsafe.getAndAddInt(this,valueOffset,1);}}// Unsafe.getAndAddInt 源码publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{vgetIntVolatile(o,offset);// 读取当前值}while(!compareAndSwapInt(o,offset,v,vdelta));// CAS尝试returnv;}3.4.2 ABA 问题及解决方案ABA 问题线程1读取值 A → 线程2改为 B → 线程2又改回 A → 线程1 CAS 成功以为没变实际已经被改过。// 解决方案1版本号/时间戳// AtomicStampedReference 维护了引用 版本号对AtomicStampedReferenceIntegerrefnewAtomicStampedReference(100,0);// 初始值100初始版本号0intstampref.getStamp();// 获取当前版本号// CAS时同时检查值和版本号booleansuccessref.compareAndSet(100,200,stamp,stamp1);// 解决方案2标记// AtomicMarkableReference 维护了引用 boolean标记对AtomicMarkableReferenceIntegerref2newAtomicMarkableReference(100,false);3.5 AQS 框架⚠️ AQS 是 Java 并发包的核心ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock 等都是基于 AQS 实现的。3.5.1 AQS 核心思想AQSAbstractQueuedSynchronizer是一个模板方法模式的框架核心是两个要素●stateint 类型表示同步状态通过 CAS 修改●CLH 队列双向链表变体存放等待获取锁的线程// AQS 核心结构publicabstractclassAbstractQueuedSynchronizer{// 同步状态volatile保证可见性privatevolatileintstate;// CLH队列的头节点和尾节点privatetransientvolatileNodehead;privatetransientvolatileNodetail;// 需要子类实现的方法模板方法模式protectedbooleantryAcquire(intarg);// 独占式获取protectedbooleantryRelease(intarg);// 独占式释放protectedinttryAcquireShared(intarg);// 共享式获取protectedbooleantryReleaseShared(intarg);// 共享式释放}// CLH队列节点staticfinalclassNode{volatileNodeprev;// 前驱节点volatileNodenext;// 后继节点volatileThreadthread;// 等待的线程volatileintwaitStatus;// 等待状态// CANCELLED1, SIGNAL-1, CONDITION-2, PROPAGATE-3}3.5.2 ReentrantLock 实现原理// ReentrantLock 基于 AQS SyncFairSync / NonfairSync// 以非公平锁为例finalbooleannonfairTryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();if(c0){// 非公平直接CAS抢锁不判断队列中是否有等待的线程if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(currentgetExclusiveOwnerThread()){// 可重入state 1intnextccacquires;setState(nextc);returntrue;}returnfalse;}// 公平锁的区别CAS前先检查是否有等待更久的线程// if (!hasQueuedPredecessors() compareAndSetState(0, acquires))// 释放锁protectedfinalbooleantryRelease(intreleases){intcgetState()-releases;if(Thread.currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;if(c0){// 可重入计数归零才真正释放freetrue;setExclusiveOwnerThread(null);}setState(c);returnfree;}