是什么大仙风把你给刮来了🍃?
快来看看有没有你感兴趣的东西,我亲爱的朋友😉。
1. 悲观锁 vs 乐观锁(心态问题) 悲观锁:觉得肯定有人抢厕所,进去就反锁门(加锁),用完再开。 → Java里的 synchronized 和 ReentrantLock 都是这种。安全,但慢。 乐观锁:觉得没人抢,不锁门,但上厕所时盯着门把手(版本号/时间戳),如果发现被人动过(冲突),就重试。 → Java里的 CAS(比较并交换) 就是这种,比如 AtomicInteger。快,但冲突多时会反复重试。 2. 公平锁 vs 非公平锁(排队问题) 公平锁:先来后到,乖乖排队。 → new ReentrantLock(true)。公平,但效率低(大家都得排队)。 非公平锁:新来的可以插队,如果锁刚好释放,它就直接抢。 → synchronized 和默认的 ReentrantLock 都是非公平。效率高,但可能导致某些线程饿死(一直抢不到)。 3. 可重入锁(同一个人的多次进入) 你进了厕所A,发现里面还有个小隔间B,你能直接进B,不用再掏钥匙。 → synchronized 和 ReentrantLock 都支持。同一个线程可以多次获取同一把锁,防止自己把自己卡死。 4. 读写锁(分情况管理) 厕所分蹲位(写锁)和洗手池(读锁)。 多人同时洗手(读)没问题。 但有人蹲坑(写)时,别人既不能蹲也不能洗手(全阻塞)。 → ReentrantReadWriteLock。适合读多写少的场景。 5. 共享锁 vs 排他锁(权限级别) 排他锁(写锁):厕所门一锁,谁也别进。 共享锁(读锁):可以多人同时看同一份文件(但不能改)。 → 读写锁就是共享/排他的典型实现。 6. 偏向锁 → 轻量级锁 → 重量级锁(锁升级,JVM自动优化) 这是 synchronized 的底层升级过程(为了性能): 偏向锁:厕所只认你一个人,你每次来都不用掏钥匙(无竞争)。 轻量级锁:偶尔有别人来,你们用“自旋”方式(原地转圈等)抢,不挂起线程(省资源)。 重量级锁:竞争激烈,排队挂起(操作系统介入,慢)。 → 这是JVM自动做的,你不用管,但知道它存在即可。 7. 自旋锁(不睡觉,死等) 厕所被占,你不去排队睡觉,而是在门口原地转圈(循环检查),等它释放。 → 适合持有锁时间很短的情况,避免线程挂起/唤醒的开销。Java里的 CAS 就是自旋思想。 8. 分段锁(分块管理) 一个大厕所分成多个小隔间,锁只锁其中一间,不影响其他间。 → ConcurrentHashMap 早期就是用分段锁,提升并发度(现在改用CAS+细粒度锁了)。 一张图总结(按使用场景选): 场景 推荐锁 简单同步,代码少 synchronized 需要可中断、超时、公平等灵活功能 ReentrantLock 读多写少(如缓存) ReentrantReadWriteLock 计数器、自增等简单操作 AtomicXXX(乐观锁) 追求极致性能,竞争不激烈 偏向锁/轻量级锁(JVM自动)
Kafka Kafdrop services: kafdrop: image: obsidiandynamics/kafdrop container_name: kafdrop ports: - "9000:9000" environment: KAFKA_BROKERCONNECT: "localhost:9092" SERVER_SERVLET_CONTEXTPATH: "/" restart: unless-stopped KRaft 模式 services: kafka: image: apache/kafka:3.9.0 container_name: kafka hostname: kafka ports: - "9092:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 # 注意:这里不要换行,不要逗号 KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 # 外部访问地址 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk volumes: - ./data:/var/lib/kafka/data restart: unless-stopped
在 Java 的世界里,“分代年龄”(Age) 是专门为 JVM 垃圾回收(GC)设计的一个计数器。 简单来说,它记录了一个 Java 对象在垃圾回收中活过了多少轮。就像打游戏刷副本一样,对象每在垃圾回收的“清洗”中幸存下来一次,它的分代年龄就会 +1。 1. 为什么需要“分代年龄”? 这源于 Java 垃圾回收领域著名的 弱分代假说(Weak Generational Hypothesis): 绝大多数的 Java 对象都是“朝生夕死”的。 比如你在一个方法里 new 了一个局部变量,方法结束了,这个对象就没用了。但也有极少数对象(如 Spring 的 Bean、数据库连接池)会一直存活。 为了高效管理内存,JVM 把堆内存分成了两大区域: 新生代(Young Generation): 存放刚出生、寿命短的对象。 老年代(Old Generation): 存放寿命长、常驻内存的对象。 “分代年龄”就是对象从“新生代”晋升到“老年代”的资格证和进度条。 2. 它的工作流程是怎样的? 我们可以把新生代想象成一个“新手村”,老年代想象成“满级主城”: 出生(0岁): 绝大多数新对象在新生代的 Eden(伊甸园)区 出生。 第一次渡劫(1岁): 发生了一次 Minor GC(新生代垃圾回收)。如果这个对象没被回收,它会幸存下来,被移动到 Survivor(幸存者)区,同时它的分代年龄变成 1 岁。 继续熬资历(每活过一轮 +1岁): 以后每发生一次 Minor GC,只要它还在幸存者区折腾且没死掉,它的年龄就会加 1 岁。 晋升老年代: 当它的年龄达到一定阈值(默认是 15 岁,由 JVM 参数 -XX:MaxTenuringThreshold 决定)时,JVM 就会认为:“这小伙子挺能活,应该是个核心常驻对象。” 于是把它晋升(Promote)到老年代。 3. 它存在对象的什么地方? 正如我们在讨论“锁”时提到的,分代年龄存在于每个 Java 对象的 对象头(Object Header)的 Mark Word 里面。 ...
1. 为什么当初要这样设计?(设计初衷) Java 诞生于 20 世纪 90 年代中期,当时多线程编程(并发)正在兴起。Java 的设计者高斯林(James Gosling)希望 Java 成为一门简单易用的多线程语言。 简化并发编程 如果对象没有内置锁,你每次想给一段代码加锁,都必须显式地创建一个 Lock 对象: // 如果没有内置锁,你得这么写: Lock myLock = new ReentrantLock(); void doSomething() { myLock.lock(); try { // 业务逻辑 } finally { myLock.unlock(); } } 而 Java 设计者引入了 synchronized 关键字,直接让对象充当锁,把复杂的同步简化成了: // 极简的同步写法 synchronized(this) { // 业务逻辑 } “万物皆对象,万物皆可为锁”的设计,让开发者不需要管理一堆乱七八糟的锁对象,只要拿到目标对象,就能直接进行同步控制。 2. 这样不浪费内存吗?JVM 是怎么“偷懒”的? 你担心的内存浪费,JVM 工程师早就想到了。他们绝对不会傻到给每个刚 new 出来的对象都分配一个沉重的操作系统级别的锁(Monitor)。 JVM 解决这个问题的核心思路是:按需分配,动态升级。 秘密武器:对象头(Object Header) 在 Java 中,每个对象的内存结构里都有一个叫 Mark Word(标记字) 的区域(在 64 位虚拟机上占 8 个字节 / 64 位)。 ...
在 Java 后端开发面试或源码阅读中,HashMap 永远是避不开的核心。很多人能熟练地背出它的扩容阈值、红黑树化条件,但当深入到源码底层,看到诸如 e.hash & (newCap - 1) 和 e.hash & oldCap 这样的位运算时,往往会陷入沉思。 一、 起源:为什么数组长度必须是 2 的次幂? 在散列表中,为了让数据均匀分布,最直观的想法是对哈希值进行取模(求余数): $$\text{索引位置} = \text{hash} \pmod{\text{Capacity}}$$然而,在 CPU 的底层底层执行中,除法和取模(%)是非常昂贵的算术操作(可能需要几十个时钟周期)。为了追求极致的性能,底层的数论定理为我们提供了一个完美的替代方案: 数学定理:当容量 $C$ 是 $2$ 的次幂($2^n$)时,对于任意整数 $H$,满足: $$H \pmod C \equiv H \ \& \ (C - 1)$$ 位运算(&)在 CPU 中只需要 1 个时钟周期!为了享受到这个性能红利,HashMap 在源码中将容量死死限制为 2 的次幂: // 默认初始容量 16 (2的4次方) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 裁剪器的魔术:hash & (newCap - 1) 以容量 16 为例,16 - 1 = 15(二进制:0000 1111)。任何 Hash 值与 15 进行 & 运算,高位都会被无情“抹零”,只有最后 4 位被保留下来: ...
主流算法 固定窗口计数器 每秒一个计数器,+1超过阈值就拒绝 缺点 0.51s 和 1.51s 之间产生2倍流量 滑动窗口 把时间切成更细的小格,滑动统计 缺点 解决临界问题,但是实现复杂 漏桶 请求进桶,匀速流出,桶满则弃 缺点流出速度恒定,扛不了正常的突发 令牌桶 匀速往桶里放令牌,来请求拿令牌,没令牌被拒绝 缺点允许一定突发
1. 屏幕高度必须与视线平齐 屏幕上缘应接近或略低于眼睛水平线,禁止长期低头使用设备。 2. 上肢支撑优先于肩部用力 键盘与鼠标位置需确保手肘自然下垂约90度,避免肩部抬起或前伸代偿。 3. 头部位置以“耳朵对肩”为标准 耳朵应位于肩峰正上方或略后方,一旦出现前伸即为错误姿势。 4. 静态坐姿持续时间不得超过45分钟 每30–45分钟必须进行至少30秒站立或活动调整,打断颈部静态负荷。 5. 禁止长时间塌陷式坐姿 避免骨盆后倾、胸椎塌陷及“窝在椅子里”使用电脑的姿势。 6. 每日进行基础颈深屈肌激活训练 执行下巴后收(chin tuck)10次,用于恢复头颈中立位控制能力。 核心原则 保持头颈中立位、减少前伸负荷、避免长时间静态固定。
什么是一般连拍和速度优先连拍? 在[速度优先连拍]模式下,当半按下快门按钮时,为第一张影像决定对焦,并且后续拍摄的对焦被锁定。