学习笔记11——并发编程之并发关键字
并发关键字
synchronized关键字
在应用Sychronized关键字时需要把握如下注意点:
1.一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
2.每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
3.synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
synchronized关键字的基本用法
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; ---对象锁
synchronized (new Object()) {System.out.println("block1锁,我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");} synchronized (new Object()) {System.out.println("block2锁,我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");}
这种情况两个代码块获取的是两把锁
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; --对象锁
-
修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; ---类锁
-
修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。--类锁
-
修饰一个代码块,synchronized(),括号中如果是class对象,则获取的是类锁。
synchronized关键字的实现原理
synchronized
是 Java 中实现线程同步的核心机制,其底层实现基于 对象监视器(Monitor)、对象头中的锁状态标记 和 锁升级优化。以下从 JVM 层、操作系统层和硬件层逐步解析其实现原理。
1.对象监视器:
Monitor 是 JVM 实现同步的核心机制,每个对象关联一个 Monitor。其结构包括:
-
Owner:当前持有锁的线程。
-
EntryList:等待锁的线程队列(处于
BLOCKED
状态)。 -
WaitSet:调用
wait()
后进入等待的线程队列(处于WAITING
或TIMED_WAITING
状态)。
Monitor 的工作流程:
-
线程尝试通过 CAS 修改对象头获取锁:
-
若成功,设置 Owner 为当前线程,进入临界区。
-
若失败,线程进入 EntryList 阻塞等待。
-
-
释放锁时,Owner 清空,唤醒 EntryList 中的线程重新竞争。
从图中可以看出,每个线程对Object对象的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。
2.对象头与状态标记:
每个Java对象在内存中分为3部分
-
对象头(Header):存储锁状态、GC 信息、哈希码等。
-
实例数据(Instance Data):对象的成员变量。
-
对齐填充(Padding):确保对象按 8 字节对齐。
对象头结构
-
Mark Word(64 bits):存储锁状态、线程 ID、GC 分代年龄等。
-
Klass Pointer(64 bits):指向类元数据的指针。
3.锁升级优化
synchronized实现的同步锁,1.6之前称之为重量级锁,重量锁会直接使用操作系统的底层的锁,会造成线程排队(串行执行),且会使CPU在用户态和核心态之间频繁切换,所以代价高、效率低。为了提高效率,从Java 1.6开始,JVM进行了优化,synchronized不一定直接使用重量锁,一共有四种状态:无锁、偏向锁、轻量级锁和重量级锁。
锁膨胀方向:无锁——>偏向锁——>轻量级锁——>重量级锁 锁只可以升级不可降级
偏向锁:
核心思想:偏向于第一个获取锁的线程,锁对象会记住首次访问它的线程 ID(记录在对象头的 Mark Word 中)。若后续没有其他线程竞争,该线程再次进入同步块时 无需加锁/解锁操作,仅需检查线程 ID 是否匹配。
1. 初次获取锁
-
CAS 设置线程 ID:通过 CAS 操作将当前线程 ID 写入对象的 Mark Word。
-
标记为偏向模式:对象头中的锁标志位更新为偏向锁状态(
101
)。
2. 再次进入同步块
-
检查线程 ID:判断对象头中的线程 ID 是否与当前线程一致:
-
✅ 一致:直接执行代码,无任何同步开销。
-
❌ 不一致:检查对象是否仍可偏向:
-
可偏向:尝试通过 CAS 竞争锁(重新偏向)。
-
已偏向其他线程:触发 偏向锁撤销,可能升级为轻量级锁。
-
-
偏向锁是针对于单个线程而言的,线程获得锁之后就不会再有解锁等操作
了,这样可以省略很多开销。假如有两个线程来竞争该锁话,那么偏向锁就失效了,进而升级成轻量级锁了
轻量锁: JVM 在存在低强度线程竞争时采用的锁优化机制,其核心是通过 CAS 自旋 减少线程阻塞的开销,避免直接升级为重量级锁(操作系统互斥锁)。
核心思想:通过线程的 CAS 自旋(循环尝试获取锁)替代直接阻塞线程,降低上下文切换的开销。适用于 线程交替执行同步块、竞争短暂且稀疏 的场景。
1. 加锁过程
-
创建 Lock Record
-
线程进入同步块时,在栈帧中分配一个 Lock Record,用于保存锁对象的原始 Mark Word。
-
-
CAS 竞争锁
-
尝试通过 CAS 操作将对象头的 Mark Word 更新为指向 Lock Record 的指针:
-
✅ 成功:对象头的锁标志位变为
00
(轻量级锁状态),线程获得锁。 -
❌ 失败:说明存在竞争,触发 自旋重试 或 锁升级。
-
-
2. 解锁过程
-
CAS 还原 Mark Word
-
通过 CAS 将 Lock Record 中保存的原始 Mark Word 写回对象头。
-
-
还原成功
-
对象恢复为无锁状态(标志位
01
)。
-
-
还原失败
-
说明锁已升级为重量级锁,需通过操作系统级别的锁机制释放。
-
轻量级锁的升级与自旋优化
所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。注意,锁在原地循环的时候,是会消耗cpu的,就相当于在执行一个啥也没有的for循环。所以,轻量级锁适用于那些同步代码块执行的很快的场景,这样,线程原地等待很短很短的时间就能够获得锁了。但是也有问题,1.如果同步代码块执行的很慢,需要消耗大量的时间,那么这个时侯,其他线程在原地等待空消耗cpu。2.本来一个线程把锁释放之后,当前线程是能够获得锁的,但是假如这个时候有好几个线程都在竞争这个锁的话,那么有可能当前线程会获取不到锁,还得原地等待继续空循环消耗cup,甚至有可能一直获取不到锁
1. 自旋策略
-
固定次数自旋:早期 JVM 采用固定次数的自旋(如 10 次),若失败则升级为重量级锁。
-
自适应自旋(Adaptive Spinning):JDK 1.6 后引入,根据 历史自旋成功率动态调整自旋次数(如上次成功则增加次数,失败则减少)。
2. 升级条件
-
自旋失败:多次 CAS 尝试后仍无法获取锁。
-
竞争加剧:超过 JVM 自旋阈值(由
-XX:PreBlockSpin
控制,默认值因 JVM 实现而异)。 -
升级路径:轻量级锁 → 重量级锁(线程阻塞,依赖操作系统互斥量)。
重量锁:是 JVM 在锁竞争激烈时的最终锁机制,其核心是 依赖操作系统互斥量(Mutex)和条件变量(Condition Variables)实现线程同步,通过线程阻塞和唤醒机制解决高并发竞争问题。当轻量级锁自旋失败(线程竞争激烈)时,升级为重量级锁,通过 操作系统内核调度 管理线程阻塞与唤醒。操作系统级别的锁机制通常支持公平性策略(如 FIFO 队列),避免线程饥饿。
synchronized关键字的特性
-
可重入性:又称递归锁,同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁。前提锁对象是同一个对象或class,不会因为之前已经获取就阻塞。
public class ReentrantDemo {public synchronized void methodA() {methodB(); // 可重入:直接进入 methodB 的同步块}public synchronized void methodB() {// 代码逻辑} }
-
内存可见性:线程释放锁时,会将共享变量的修改刷新到主内存;获取锁时,会从主内存重新加载变量值
-
有序性:禁止指令重排序:临界区内的代码不会被编译器或处理器重排序破坏逻辑。
synchronized关键字的优化策略
1. 锁消除(Lock Elimination)
-
触发条件:JIT 编译器检测到不存在共享数据竞争的锁。
-
示例:局部对象锁(线程私有,无需同步)。
public void lockEliminationDemo() {Object localLock = new Object();synchronized (localLock) { // 锁被消除System.out.println("This lock is unnecessary");} }
2. 锁粗化(Lock Coarsening)
-
触发条件:多次连续的锁操作合并为一次,减少锁开销。
-
示例:循环内重复加锁。
public void lockCoarseningDemo() {synchronized (this) {// 合并多次锁操作为一次for (int i = 0; i < 100; i++) {// 操作共享资源}} }
3. 自旋优化(Adaptive Spinning)
-
轻量级锁失败后:线程不立即阻塞,而是自旋重试(默认次数为 10 次,JDK 6 后改为自适应)。
volatile关键字
volatile
是 Java 中用于解决 多线程内存可见性 和 指令重排序 问题的关键字。它提供了一种轻量级的同步机制,确保变量的修改对所有线程立即可见,同时禁止编译器和处理器对代码进行某些优化。注意不保证复合操作的原子性,需结合锁或原子类使用。
volatile的核心作用
-
保证可见性:在多线程环境下,每个线程可能将共享变量缓存到自己的 工作内存(CPU 缓存) 中,导致一个线程修改了变量的值,其他线程无法立即看到最新值;若使用volatile修饰变量,每次读写都直接操作 主内存,绕过线程的工作内存。强制其他线程在读取
volatile
变量时,清空本地缓存,重新从主内存加载最新值。public class VisibilityDemo {private volatile boolean flag = false; public void writer() {flag = true; // 写操作立即刷新到主内存} public void reader() {while (!flag) { // 每次读取都从主内存加载最新值// 循环等待}System.out.println("Flag is now true");} }
-
禁止指令重排序
-
问题背景: 编译器和处理器为了提高性能,可能会对代码执行顺序进行 重排序(如单例模式中的双重检查锁定问题)。
-
volatile 的解决方案:
-
通过插入 内存屏障(Memory Barrier),禁止对
volatile
变量前后的指令进行重排序。 -
确保
volatile
变量的写操作对其他线程可见的顺序符合程序预期。
-
public class Singleton {private static volatile Singleton instance; public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); // 禁止重排序,确保对象完全初始化}}}return instance;} }
-
volatile的实现原理
1. 内存屏障(Memory Barrier):又称内存栅栏,是一个cpu指令。
JVM 会在 volatile
变量的读写操作前后插入特定类型的内存屏障,确保以下两点:
-
可见性:强制将工作内存的修改刷新到主内存,或从主内存重新加载变量值。
-
有序性:禁止编译器或处理器对指令进行重排序。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1、L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写到系统内存。为了保证各个处理器的缓冲是一致的,实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓冲的值是不是过期了,当处理器发现自己缓存行对应的 内存的地址被修改了,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓冲中。所有多核处理器发现本地缓存失效后,就会从内存中重读该变量的数据,也就获取到了最新的值。
屏障类型 | 作用 |
---|---|
LoadLoad | 确保当前读操作之前的其他读操作已完成。 |
StoreStore | 确保当前写操作之前的其他写操作对其他线程可见。 |
LoadStore | 确保当前读操作之后的写操作不会被重排序到读操作之前。 |
StoreLoad | 确保当前写操作之后的所有读/写操作不会被重排序到写操作之前(全能屏障,开销最大)。 |
2. 具体规则
-
写操作(Write): 在写
volatile
变量后插入 StoreStore 和 StoreLoad 屏障,确保:-
当前变量的修改对其他线程可见。
-
写操作不会被重排序到后续操作之后。
-
-
读操作(Read): 在读
volatile
变量前插入 LoadLoad 和 LoadStore 屏障,确保:-
后续操作不会被重排序到读操作之前。
-
每次读取都能获取最新值。
-
volatile的使用场景
1.状态标志位
多线程中通过 volatile
变量作为开关控制线程执行。
public class TaskRunner implements Runnable {private volatile boolean running = true; public void stop() {running = false; // 其他线程调用此方法后,立即停止任务} @Overridepublic void run() {while (running) {// 执行任务}} }
2.单例模式
通过 volatile
解决双重检查锁定中的重排序问题
3.无锁编程
volatile 与 synchronized 的对比
维度 | volatile | synchronized |
---|---|---|
可见性 | 保证变量的可见性 | 保证临界区内所有变量的可见性 |
原子性 | 仅单次读/写操作原子 | 保证代码块内操作的原子性 |
有序性 | 禁止指令重排序 | 通过锁机制隐式保证有序性 |
性能 | 轻量级(无上下文切换开销) | 重量级(涉及锁升级和线程阻塞) |
适用场景 | 状态标志、单次发布、无锁编程 | 复杂同步逻辑、复合操作 |
final关键字
final的作用
修饰类:禁止类被继承(即不可有子类)。
修饰方法:禁止方法被子类重写(Override)。但是可以重载。
修饰变量:
-
基本类型变量:变量值不可修改,必须在声明时或构造方法中初始化。
final int MAX_VALUE = 100; // 声明时初始化 final double PI; public MyClass() { PI = 3.14; } // 构造方法中初始化
-
引用类型变量:引用指向的对象不可变,但对象内部状态可能可变。
final List<String> list = new ArrayList<>(); list.add("Java"); // 允许操作对象内容 // list = new LinkedList<>(); // 编译错误,禁止重新赋值
-
常量定义:全局常量
public static final String LOG_TAG = "System";
相关文章:

学习笔记11——并发编程之并发关键字
并发关键字 synchronized关键字 在应用Sychronized关键字时需要把握如下注意点: 1.一把锁只能同时被一个线程获取,没有获得锁的线程只能等待; 2.每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁…...

2.2 Windows本地部署DeepSeek模型 --- Ollama篇(下)
2.3Ollama加载已下载Deepseek模型 无网络连接,直接通过Ollama本地已经本地已经下载好的的Deepseek模型。 2.3.1 查看已安装模型 PS C:\Users\Administrator> ollama list NAME ID SIZE MODIFIED deepseek-r1:8…...

DeepSeek R1-32B医疗大模型的完整微调实战分析(全码版)
DeepSeek R1-32B微调实战指南 ├── 1. 环境准备 │ ├── 1.1 硬件配置 │ │ ├─ 全参数微调:4*A100 80GB │ │ └─ LoRA微调:单卡24GB │ ├── 1.2 软件依赖 │ │ ├─ PyTorch 2.1.2+CUDA │ │ └─ Unsloth/ColossalAI │ └── 1.3 模…...

mysql的锁--一篇读懂所有锁机制
目录 mysql的锁 概述:根据mysql锁的大类型可以分为 我们先来讲一下范围最大的全局锁 使用 为什么要使用全局锁? 使用全局锁进行备份的缺点 表级锁 表锁 1.共享读表锁的语法 2.排斥写表锁 元数据锁 意向锁 什么是意向锁 怎么产生意向锁 意向…...

LTC6804、LTC6811、LTC6813的使用
FSEC自制BMS第一步:从零开发使用LTC6804采集电池电压 LTC6811特性 LTC6811 是 LTC6804 的引脚兼容型升级器件,LTC6804官方已经不推荐选用 可测量多达 12 节串联电池 1.2mV 最大总测量误差 可堆叠式架构能支持几百个电池 内置 isoSPI™ 接口 可在 290μ…...

linux内存页块划分及位图存储机制
page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer 一. 什么是页块(Pageblock)? 定义:页块是物理内存中的一个连续区域,由 2^pageblock_order 个物理页(Pag…...
Vue 文件下载功能的跨域处理与前后端实现详解
在 Web 应用开发中,文件下载功能是常见需求。但由于跨域限制和认证机制的复杂性,实际开发中常遇到下载失败或权限错误等问题。本文将结合 Vue 前端和 Spring Boot 后端,详细介绍文件下载功能的实现与跨域问题的解决方案。 一、问题背景 在某…...

boost::beast websocket 实例
环境:ubuntu 1. 安装boost sudo apt install -y libboost-all-dev 2. Server端 #include <boost/asio.hpp> #include <boost/beast.hpp> #include <iostream> #include <thread>namespace beast boost::beast; // 从 Boost.Beast 中导…...

复试难度,西电卓越工程师学院(杭研院)考研录取情况
01、卓越工程师学院各个方向 02、24卓越工程师学院(杭研院)近三年复试分数线对比 PS:卓越工程师学院分为广研院、杭研院 分别有新一代电子信息技术、通信工程、集成电路工程、计算机技术、光学信息工程、网络信息安全、机械,这些…...

Rabbitmq--延迟消息
13.延迟消息 延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才会收到消息 延迟任务:一定时间之后才会执行的任务 1.死信交换机 当一个队列中的某条消息满足下列情况之一时,就会…...

cocos creator使用mesh修改图片为圆形,减少使用mask,j减少drawcall,优化性能
cocos creator版本2.4.11 一个mask占用drawcall 3个以上,针对游戏中技能图标,cd,以及多玩家头像,是有很大优化空间 1.上代码,只适合单独图片的,不适合在图集中的图片 const { ccclass, property } cc._decorator;c…...

C++ Qt开发成长之路,从入门到企业级实战项目,保姆级学习路线
Qt 介绍 Qt是一个跨平台的C图形用户界面应用程序开发框架,最初由挪威的Trolltech公司开发,后来被诺基亚收购,现在由Qt公司维护。它提供了丰富的工具和类库,使开发者能够轻松地创建各种类型的应用程序,包括桌面应用、移…...

JavaWeb后端基础(7)AOP
AOP是Spring框架的核心之一,那什么是AOP?AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。AOP是一种思想,而在Spring框…...
Uniapp实现地图获取定位功能
摘要:本文将手把手教你如何在Uniapp项目中集成地图功能、实现定位获取,并解决微信小程序、APP、H5三端的兼容性问题🚀🚀🚀 一、环境准备 地图平台选择 微信小程序:腾讯地图(强制使用)…...

批量将 Excel 转换 PDF/Word/CSV以及图片等其它格式
Excel 格式转换是我们工作过程当中非常常见的一个需求,我们通常需要将 Excel 转换为其他各种各样的格式。比如将 Excel 转换为 PDF、比如说将 Excel 转换为 Word、再比如说将 Excel文档转换为图片等等。 这些操作对我们来讲都不难,因为我们通过 Office 都…...
Flutter:StatelessWidget vs StatefulWidget 深度解析
目录 1. 引言 2. StatelessWidget(无状态组件) 2.1 定义与特点 2.2 代码示例 3. StatefulWidget(有状态组件) 3.1 定义与特点 3.2 代码示例 4. StatelessWidget vs StatefulWidget 对比 5. StatefulWidget 生命周期 5.1…...
Stream流学习
Stream流 把数据放进stream流水线,对数据进行一系列操作(中间方法),最后封装(终结方法)。 Stream.of()允许传入任何参数 常见中间方法 可以对数据进行链式(流水线)操作,但…...
多视图几何--恢复相机位姿/内参的几种方法
恢复相机位姿的几种方法 1分解投影矩阵 1.1投影矩阵分解为相机内外参矩阵的完整解析 投影矩阵(Projection Matrix)是计算机视觉中将三维世界点映射到二维像素坐标的核心工具,其本质是相机内参矩阵(Intrinsic Matrix)…...

[数据结构]堆详解
目录 一、堆的概念及结构 二、堆的实现 1.堆的定义 2堆的初始化 3堆的插入 编辑 4.堆的删除 5堆的其他操作 6代码合集 三、堆的应用 (一)堆排序(重点) (二)TOP-K问题 一、堆的概念及结构 堆的…...
领域驱动设计(DDD)与MVC架构:理念对比与架构选择
领域驱动设计(DDD)与MVC架构:理念对比与架构选择 一、架构之争的本质:业务复杂度驱动技术演进 在软件开发领域,没有银弹式的完美架构,只有适合当前业务场景的合理选择。MVC与DDD的区别本质上是业务复杂度与…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...