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,任务处理流程

      1. 核心线程数检查
        当前运行的线程数小于核心线程数(corePoolSize),线程池会创建一个新的线程来执行任务。
        如果核心线程数已满,任务会被放入工作队列(workQueue)
      1. 工作队列检查
        如果工作队列未满,任务会被放入队列中等待执行
        如果工作队列已满,线程池会尝试创建新的线程(不超过 maximumPoolSize 的线程数)
      1. 最大线程数检查
        如果当前线程已达最大线程数,且工作队列已满,线程池会执行拒绝策略。
      1. 任务执行
        线程从任务队列中获取任务并执行。
        如果任务队列为空,线程会等待新的任务到来(通过阻塞队列的take()方法)。
      1. 线程回收
        如果线程池中的线程数量超过核心线程数,且线程空闲时间超过keepAliveTime,多余的线程会被回收,直到线程数量降至核心线程数。
        核心线程默认不会被回收,除非设置了allowCoreThreadTimeOut(true),此时核心线程在空闲时间超过keepAliveTime后也会被回收。
  • 4,线程池关闭

    调用shutdown()方法后,线程池会停止接收新任务,并等待已提交的任务执行完毕。

    调用shutdownNow()方法后,线程池会尝试中断正在执行的任务,并返回未执行的任务列表。

总结:
Java 线程池的执行流程可以概括为:
提交任务。
检查核心线程数,创建新线程或放入队列。
检查队列和最大线程数,决定是否创建新线程或触发拒绝策略。
线程执行任务。
空闲线程回收。
线程池关闭。

线程池 execute 和 submit 区别?

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 中)。

java 线程的六种状态

    1. NEW(新建状态)线程已创建尚未启动
    1. RUNNABLE(可运行状态)调用 start() 方法后
    1. BLOCKED(阻塞状态)试图进入同步代码块/方法、锁已被其他线程持有
    1. WAITING(无限期等待)Object.wait()、Thread.join()、LockSupport.park()
    1. TIMED_WAITING(限期等待)Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()
    1. TERMINATED(终止状态)run() 方法执行完成、未捕获异常导致线程终止

sleep() 和 wait() 的区别?

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

    1. 对象分配:新对象首先尝试在Eden区分配

    2. Eden区满时:触发Minor GC

    3. 标记:标记Eden区和From Survivor区存活对象

    4. 复制:将存活对象复制到To Survivor区

    5. 年龄增加:存活对象的年龄计数器+1

    6. 交换:From和To Survivor区角色交换

    7. 晋升:当对象年龄超过阈值(默认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进行指令重排序(内存屏障)

volatile不能一定能保证线程安全,因为不能保证操作的原子性。

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 实现自己的类加载器

双亲委派机制的流程

  1. 当一个类加载器收到类加载请求时,首先检查这个类是否已经被加载过

  2. 如果没有被加载过,将加载请求委派给父类加载器去完成

  3. 父类加载器同样会检查并委派给自己的父类加载器

  4. 这个委派过程会一直向上传递,直到启动类加载器

  5. 如果父类加载器无法完成加载(在自己的搜索范围内找不到该类),子加载器才会尝试自己去加载

双亲委派机制的作用

  1. 安全性:系统核心类的安全由Bootstrap ClassLoader负责,防止恶意代码篡改核心API。

  2. 模块化:通过明确的层次结构,可以更好地管理和控制类的访问和定义。

  3. 灵活性:允许自定义的类加载器介入类的加载过程,同时保持核心功能的稳定和安全。、

打破双亲委派机制的情景

  • SPI(Service Provider Interface)机制:

    • 如 JDBC、JNDI 等服务的加载

    • 使用线程上下文类加载器(Thread Context ClassLoader)

  • 热部署/热替换

    • 如 Tomcat 等 Web 容器

    • 每个 Web 应用使用独立的类加载器

7. java 中的锁机制

java 中锁的作用是什么?

  1. 保证多线程环境下对共享资源的互斥访问

  2. 解决并发环境下的数据一致性问题

  3. 协调线程间的执行顺序

synchronized 关键字用法

  1. 修饰实例方法:锁是当前实例对象
public synchronized void method() {}
  1. 修饰静态方法:锁是当前类的 Class 对象
public static synchronized void staticMethod() {}
  1. 同步代码块:可以指定锁对象
synchronized(lockObject) {
    // 同步代码
}

synchronized 和 ReentrantLock 的区别?

特性 synchronized ReentrantLock
实现方式 JVM 层面实现 JDK 层面实现
锁获取方式 自动获取和释放 需要手动 lock()/unlock()
可中断 不可中断 支持 lockInterruptibly()
公平锁 非公平 可配置公平/非公平
条件变量 单一条件 支持多个 Condition
性能 Java 6 后优化,性能相当 高竞争下表现更好

锁的类型

  1. 乐观锁和悲观锁

    • 悲观锁:假定会发生并发冲突,直接加锁 (synchronized, ReentrantLock)
    • 乐观锁:假定不会冲突,通过版本号/CAS 实现 (AtomicInteger, StampedLock)
  2. 可重入锁

    • 同一线程可以重复获取已经持有的锁
    • synchronized 和 ReentrantLock 都是可重入锁
  3. 公平锁和非公平锁

    • 公平锁:按照线程请求顺序获取锁
    • 非公平锁:允许插队,可能造成饥饿但吞吐量更高
    • ReentrantLock 可通过构造函数指定公平性,Synchronized 非公平锁
  4. 读写锁

    • 读写互斥,写写互斥
    • ReadWriteLock

死锁的产生条件及如何避免

  • 必要条件

    1. 互斥条件
    2. 请求与保持
    3. 不可剥夺
    4. 循环等待
  • 避免方法

    1. 固定获取锁顺序
    2. 使用 tryLock 设置超时
    3. 减少同步代码范围

CAS 机制(乐观锁)

  • Compare And Swap,比较并交换。

  • CAS是原子指令,一种基于锁的操作,而且是乐观锁,又称无锁机制。

  • CAS操作包含三个基本操作数:内存位置、期望值和新值。

    1. 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)。
    2. 工作内存中共享变量的副本值,也叫预期值:A。
    3. 需要将共享变量更新到的最新值: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 数据类型

Java 的基本数据类型有哪些?

  • 整型:byte(1)、short(2)、int(4)、long(8)

  • 浮点型:float(4)、double(8)

  • 字符型:char(2)

  • 布尔型:boolean(1)

自动装箱与拆箱是什么?

  • 装箱:基本类型 → 包装类型(Integer.valueOf(10))

  • 拆箱:包装类型 → 基本类型(intValue())

  • 自动转换由编译器完成

9. 集合框架

List、Set、Map 的区别?

  • List:有序可重复(ArrayList、LinkedList)

  • Set:无序不可重复(HashSet、TreeSet)

  • Map:键值对存储(HashMap、TreeMap)

HashMap 的工作原理?

  • 基于哈希表实现(数组+链表/红黑树)

  • 通过 key 的 hashCode() 计算桶位置

  • Java 8 后链表长度 >8 转换为红黑树

10. 异常处理

Error 和 Exception 的区别?

  • Error:系统级错误(如 OutOfMemoryError),程序无法处理

  • Exception:程序可处理的异常,分为:

    • 受检异常(必须处理,如 IOException)

    • 非受检异常(RuntimeException)

posted @ 2025-03-27 14:43  primaryC  阅读(41)  评论(0)    收藏  举报