JAVA基础
1. java 线程池执行流程
-
1,线程池的创建
线程池通过 ThreadPoolExecutor 类创建,或者使用 Executors 工厂类提供的便捷方法来创建不同类型的线程池(如 newFixedThreadPool、newCachedThreadPool 等)。
主要有七个参数:
public ThreadPoolExecutor(int corePoolSize, //核心线程数 int maximumPoolSize, //最大线程数 long keepAliveTime, //线程存活时间 TimeUnit unit, //线程存活时间单位 BlockingQueue<Runnable> workQueue, //任务队列 ThreadFactory threadFactory, //线程工厂 RejectedExecutionHandler handler) //拒绝策略
-
2,任务提交
调用 execute(Runnable task) 或 submit(Callable task) 方法提交任务
executor.execute(() -> { // 任务逻辑 });
-
3,任务处理流程
-
- 核心线程数检查
当前运行的线程数小于核心线程数(corePoolSize),线程池会创建一个新的线程来执行任务。
如果核心线程数已满,任务会被放入工作队列(workQueue)
- 核心线程数检查
-
- 工作队列检查
如果工作队列未满,任务会被放入队列中等待执行
如果工作队列已满,线程池会尝试创建新的线程(不超过 maximumPoolSize 的线程数)
- 工作队列检查
-
- 最大线程数检查
如果当前线程已达最大线程数,且工作队列已满,线程池会执行拒绝策略。
- 最大线程数检查
-
- 任务执行
线程从任务队列中获取任务并执行。
如果任务队列为空,线程会等待新的任务到来(通过阻塞队列的take()方法)。
- 任务执行
-
- 线程回收
如果线程池中的线程数量超过核心线程数,且线程空闲时间超过keepAliveTime,多余的线程会被回收,直到线程数量降至核心线程数。
核心线程默认不会被回收,除非设置了allowCoreThreadTimeOut(true),此时核心线程在空闲时间超过keepAliveTime后也会被回收。
- 线程回收
-
-
4,线程池关闭
调用shutdown()方法后,线程池会停止接收新任务,并等待已提交的任务执行完毕。
调用shutdownNow()方法后,线程池会尝试中断正在执行的任务,并返回未执行的任务列表。
总结:
Java 线程池的执行流程可以概括为:
提交任务。
检查核心线程数,创建新线程或放入队列。
检查队列和最大线程数,决定是否创建新线程或触发拒绝策略。
线程执行任务。
空闲线程回收。
线程池关闭。
1. 方法定义
-
execute(Runnable task):
-
定义在 Executor 接口中。
-
用于提交不需要返回结果的任务。
-
只能接受 Runnable 类型的任务。
-
-
submit(Runnable task) 或 submit(Callable
task): -
定义在 ExecutorService 接口中。
-
可以提交需要返回结果的任务。
-
可以接受 Runnable 或 Callable 类型的任务。
-
2. 返回值
-
execute:
-
没有返回值(void)。
-
适用于不需要获取任务执行结果的场景。
-
-
submit:
-
返回一个 Future 对象。
-
可以通过 Future 获取任务的执行结果或检查任务是否完成。
-
如果提交的是 Runnable 任务,Future.get() 返回 null。
-
如果提交的是 Callable 任务,Future.get() 返回任务的计算结果。
-
3. 异常处理
-
execute:
-
如果任务执行过程中抛出未捕获的异常,线程会终止。
-
异常需要通过自定义的 ThreadFactory 或 UncaughtExceptionHandler 处理。
-
-
submit:
-
如果任务执行过程中抛出异常,异常会被捕获并存储在 Future 对象中。
-
调用 Future.get() 时,异常会重新抛出(包装在 ExecutionException 中)。
-
-
- NEW(新建状态)线程已创建尚未启动
-
- RUNNABLE(可运行状态)调用 start() 方法后
-
- BLOCKED(阻塞状态)试图进入同步代码块/方法、锁已被其他线程持有
-
- WAITING(无限期等待)Object.wait()、Thread.join()、LockSupport.park()
-
- TIMED_WAITING(限期等待)Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()
-
- TERMINATED(终止状态)run() 方法执行完成、未捕获异常导致线程终止
1. 所属类不同
-
sleep():
-
是 Thread 类的静态方法
-
Thread.sleep(long millis)
-
-
wait():
-
是 Object 类的实例方法
-
object.wait()
-
2. 锁行为不同(最关键区别)
-
sleep():
-
不会释放锁(保持持有的所有监视器锁)
-
即使线程睡眠,其他线程也无法获取该线程持有的锁
-
-
wait():
-
会释放锁(放弃对象监视器)
-
允许其他线程获取该对象的锁
-
3. 使用条件
-
sleep():
-
可以在任何地方调用
-
不需要在同步代码块中
-
-
wait():
-
必须在同步代码块中调用(即必须先获得对象锁)
-
否则会抛出 IllegalMonitorStateException
-
4. 唤醒机制
-
sleep():
-
睡眠指定时间后自动唤醒
-
也可以通过 interrupt() 中断唤醒
-
-
wait():
-
需要其他线程调用 notify()/notifyAll() 唤醒
-
可以设置超时时间 wait(long timeout)
-
也可以通过 interrupt() 中断唤醒
-
2. java 有哪些线程安全的类
1. java.util.concurrent 包中的类
-
ConcurrentHashMap: 线程安全的哈希表,支持高并发读写。
-
CopyOnWriteArrayList: 线程安全的列表,适用于读多写少的场景。
-
CopyOnWriteArraySet: 线程安全的集合,基于 CopyOnWriteArrayList 实现。
-
BlockingQueue 接口及其实现类:
-
ArrayBlockingQueue: 基于数组的有界阻塞队列。
-
LinkedBlockingQueue: 基于链表的可选有界阻塞队列。
-
PriorityBlockingQueue: 支持优先级的无界阻塞队列。
-
-
CountDownLatch: 允许一个或多个线程等待其他线程完成操作。
-
CyclicBarrier: 让一组线程互相等待,达到某个屏障点后再继续执行。
-
Semaphore: 控制同时访问特定资源的线程数量。
-
ReentrantLock: 可重入锁,提供比 synchronized 更灵活的锁机制。
-
Atomic 类:
-
AtomicInteger: 线程安全的整数操作。
-
AtomicLong: 线程安全的长整型操作。
-
AtomicReference: 线程安全的引用类型操作。
-
2. StringBuffer
- StringBuffer 是线程安全的字符串操作类,所有方法都使用 synchronized 修饰。
3. Vector 和 Hashtable
-
Vector: 线程安全的动态数组,所有方法都使用 synchronized 修饰。
-
Hashtable: 线程安全的哈希表,所有方法都使用 synchronized 修饰。
3. java 垃圾回收机制
先背概念:https://www.cnblogs.com/cnff/p/17392494.html
GC 过程
主要发生在堆和方法去,较多在新生代,较少在老年代,基本不在永久代(元空间)
1. GC 基本原理
- 可达性分析算法
- GC Roots
2. 分代收集理论
-
新生代:复制算法, Minor GC
-
对象分配:新对象首先尝试在Eden区分配
-
Eden区满时:触发Minor GC
-
标记:标记Eden区和From Survivor区存活对象
-
复制:将存活对象复制到To Survivor区
-
年龄增加:存活对象的年龄计数器+1
-
交换:From和To Survivor区角色交换
-
晋升:当对象年龄超过阈值(默认15),晋升到老年代
-
-
老年代:标记-压缩算法, Major GC
-
触发条件:老年代空间不足,方法区(PermGen/Metaspace)空间不足,System.gc()调用(不一定立即执行)
-
标记阶段:标记所有存活对象
-
清除阶段:
标记-清除算法:直接清除未标记对象(会产生碎片)
标记-整理算法:移动存活对象,整理内存(减少碎片)
-
4. java 内存模型(JMM)
Java 内存模型(Java Memory Model)是 Java 多线程编程的核心概念,
Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量,不同的线程之间也无法访问对方工作内存中的变量。线程之间变量值的传递均需要通过主内存来完成。
1. JMM 的主要组成部分
-
主内存
- 所有共享变量的存储区域
- 所有线程都能访问,但线程不能直接操作主内存中的变量
-
工作内存
- 每个线程私有的内存区域
- 存储该线程使用到的变量的副本
- 线程对变量所有的操作都必须在工作内存中进行
JMM 的 8 种基本操作
-
lock (锁定):作用于主内存变量,标识为线程独占
-
unlock (解锁):作用于主内存变量,释放锁定状态
-
read (读取):从主内存传输变量到工作内存
-
load (载入):把 read 得到的值放入工作内存的变量副本
-
use (使用):把工作内存变量值传递给执行引擎
-
assign (赋值):把从执行引擎接收的值赋给工作内存变量
-
store (存储):把工作内存变量值传送到主内存
-
write (写入):把 store 得到的值放入主内存变量
JMM 三大特性
-1.原子性:指一个操作是不可中断的,即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰
- 基本数据类型的访问读写是原子性的
- synchronized 块之间的操作是原子性的(JMM只能保证基本的原子性,如果要保证一个代码块的原子性,需要用到 synchronized)
-2. 可见性
- 可见性指当一个线程修改共享变量的值,其他线程能够立即知道被修改了。Java 是利用 volatile 关键字来提供可见性的。 当变量被 volatile 修饰时,这个变量被修改后会立刻刷新到主内存,当其它线程需要读取该变量时,会去主内存中读取新值。而普通变量则不能保证这一点。
- 除了volatile关键字之外,final和synchronized也能实现可见性。
- synchronized的原理是,在执行完,进入unlock之前,必须将共享变量同步到主内存中。
- final修饰的字段,一旦初始化完成,如果没有对象逸出(指对象为初始化完成就可以被别的线程使用),那么对于其他线程都是可见的。
-3. 有序性
-
在Java中,可以使用synchronized或者volatile保证多线程之间操作的有序性。实现原理有些区别:
-
volatile关键字是使用内存屏障达到禁止指令重排序,以保证有序性。
-
synchronized的原理是,一个线程lock之后,必须unlock后,其他线程才可以重新lock,使得被synchronized包住的代码块在多线程之间是串行执行的。
-
volatile 关键字
volatile关键字,主要的作用包括两点
- 保证线程间变量的可见性,volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的。普通变量则需要重新读取才能获得最新值。
- 禁止CPU进行指令重排序(内存屏障)
5. 堆和栈的区别
1. 存储内容不同
堆:存储所有创建的对象和例和数组
栈:存储基本数据类型,对象引用和方法调用
2. 线程共享
堆:所有线程共享
栈:每个线程有自己独立的栈空间
3. 异常类型
堆:OutOfMemoryError
栈:StackOverflowError
4. 内存分配方式
堆:内存分配和回收由垃圾收集器管理,更灵活更复杂
栈:内存分配由系统自动完成,遵循 LIFO(后进先出)原则
5. 生命周期
堆:对象在程序运行时创建,由 GC 在不被引用时回收
栈:方法调用时创建栈帧,方法结束时自动释放
6. 内存大小
堆:通常较大,可通过 -Xmx(堆最大值) 和 -Xms(堆最小值) 配置
栈:通常较小,大小可通过 JVM 参数配置(-Xss)
7. 访问速度
堆:访问速度相对较慢,因为内存分配是动态的。
栈:访问速度更快,因为内存分配是连续的
6. java 双亲委派机制
什么是 java 双亲委派机制?
双亲委派机制是指:当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
类加载器层次结构
-
启动类加载器(Bootstrap ClassLoader)
最顶层的加载器,由 C++ 实现
负责加载 JAVA_HOME/lib 目录下的核心类库
-
扩展类加载器(Extension ClassLoader)
由 Java 实现,继承自 java.lang.ClassLoader
负责加载 JAVA_HOME/lib/ext 目录下的扩展类库
-
应用程序类加载器(Application ClassLoader)
也称为系统类加载器(System ClassLoader)
负责加载用户类路径(ClassPath)上的类库
-
自定义类加载器(User-Defined ClassLoader)
开发者可以继承 ClassLoader 实现自己的类加载器
双亲委派机制的流程
-
当一个类加载器收到类加载请求时,首先检查这个类是否已经被加载过
-
如果没有被加载过,将加载请求委派给父类加载器去完成
-
父类加载器同样会检查并委派给自己的父类加载器
-
这个委派过程会一直向上传递,直到启动类加载器
-
如果父类加载器无法完成加载(在自己的搜索范围内找不到该类),子加载器才会尝试自己去加载
双亲委派机制的作用
-
安全性:系统核心类的安全由Bootstrap ClassLoader负责,防止恶意代码篡改核心API。
-
模块化:通过明确的层次结构,可以更好地管理和控制类的访问和定义。
-
灵活性:允许自定义的类加载器介入类的加载过程,同时保持核心功能的稳定和安全。、
打破双亲委派机制的情景
-
SPI(Service Provider Interface)机制:
-
如 JDBC、JNDI 等服务的加载
-
使用线程上下文类加载器(Thread Context ClassLoader)
-
-
热部署/热替换
-
如 Tomcat 等 Web 容器
-
每个 Web 应用使用独立的类加载器
-
7. java 中的锁机制
java 中锁的作用是什么?
-
保证多线程环境下对共享资源的互斥访问
-
解决并发环境下的数据一致性问题
-
协调线程间的执行顺序
synchronized 关键字用法
- 修饰实例方法:锁是当前实例对象
public synchronized void method() {}
- 修饰静态方法:锁是当前类的 Class 对象
public static synchronized void staticMethod() {}
- 同步代码块:可以指定锁对象
synchronized(lockObject) {
// 同步代码
}
synchronized 和 ReentrantLock 的区别?
特性 | synchronized | ReentrantLock |
---|---|---|
实现方式 | JVM 层面实现 | JDK 层面实现 |
锁获取方式 | 自动获取和释放 | 需要手动 lock()/unlock() |
可中断 | 不可中断 | 支持 lockInterruptibly() |
公平锁 | 非公平 | 可配置公平/非公平 |
条件变量 | 单一条件 | 支持多个 Condition |
性能 | Java 6 后优化,性能相当 | 高竞争下表现更好 |
锁的类型
-
乐观锁和悲观锁
- 悲观锁:假定会发生并发冲突,直接加锁 (synchronized, ReentrantLock)
- 乐观锁:假定不会冲突,通过版本号/CAS 实现 (AtomicInteger, StampedLock)
-
可重入锁
- 同一线程可以重复获取已经持有的锁
- synchronized 和 ReentrantLock 都是可重入锁
-
公平锁和非公平锁
- 公平锁:按照线程请求顺序获取锁
- 非公平锁:允许插队,可能造成饥饿但吞吐量更高
- ReentrantLock 可通过构造函数指定公平性,Synchronized 非公平锁
-
读写锁
- 读写互斥,写写互斥
- ReadWriteLock
死锁的产生条件及如何避免
-
必要条件
- 互斥条件
- 请求与保持
- 不可剥夺
- 循环等待
-
避免方法
- 固定获取锁顺序
- 使用 tryLock 设置超时
- 减少同步代码范围
CAS 机制(乐观锁)
-
Compare And Swap,比较并交换。
-
CAS是原子指令,一种基于锁的操作,而且是乐观锁,又称无锁机制。
-
CAS操作包含三个基本操作数:内存位置、期望值和新值。
- 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)。
- 工作内存中共享变量的副本值,也叫预期值:A。
- 需要将共享变量更新到的最新值:B
-
在Java中,CAS机制被封装在jdk.internal.misc.Unsafe类中,尽管这个类并不建议在普通应用程序中直接使用,但它是构建更高层次并发工具的基础,例如java.util.concurrent.atomic包下的原子类如AtomicInteger、AtomicLong等
-
ABA 问题
CAS 操作中值从 A→B→A,看似没变实际已修改
单纯的CAS无法识别一个值被多次修改后又恢复原值的情况,可能导致错误的判断。在高并发场景下,使用CAS操作可能存在ABA问题,也就是在一个值被修改之前,先被其他线程修改为另外的值,然后再被修改回原值,此时CAS操作会认为这个值没有被修改过,导致数据不一致。
为了解决ABA问题,Java中提供了AtomicStampedReference类(原子标记参考),该类通过使用版本号的方式来解决ABA问题。每个共享变量都会关联一个版本号,CAS操作时需要同时检查值和版本号是否匹配。因此,如果共享变量的值被改变了,版本号也会发生变化,即使共享变量被改回原来的值,版本号也不同,因此CAS操作会失败
8. Java 数据类型
-
整型:byte(1)、short(2)、int(4)、long(8)
-
浮点型:float(4)、double(8)
-
字符型:char(2)
-
布尔型:boolean(1)
-
装箱:基本类型 → 包装类型(Integer.valueOf(10))
-
拆箱:包装类型 → 基本类型(intValue())
-
自动转换由编译器完成
9. 集合框架
-
List:有序可重复(ArrayList、LinkedList)
-
Set:无序不可重复(HashSet、TreeSet)
-
Map:键值对存储(HashMap、TreeMap)
-
基于哈希表实现(数组+链表/红黑树)
-
通过 key 的 hashCode() 计算桶位置
-
Java 8 后链表长度 >8 转换为红黑树
10. 异常处理
-
Error:系统级错误(如 OutOfMemoryError),程序无法处理
-
Exception:程序可处理的异常,分为:
-
受检异常(必须处理,如 IOException)
-
非受检异常(RuntimeException)
-