引入

在多线程编程的世界里,共享资源的访问控制就像一场精心设计的交通管制,而Synchronized作为Java并发编程的基础同步机制,扮演着"交通警察"的关键角色。

并发编程的核心矛盾

当多个线程同时访问共享资源时,"线程安全"问题便应运而生。想象一个银行账户的场景:若两个线程同时执行扣款操作,可能导致账户余额出现负数或不一致的情况。这种情况下,我们需要一种机制来确保在同一时刻只有一个线程能操作共享资源,这就是同步锁的核心使命。

Synchronized的历史地位

作为Java语言内置的同步机制,Synchronized从JDK1.0时代便已存在。早期版本中,它因"重量级锁"的标签被认为性能不佳,但随着JDK6之后的一系列优化(如偏向锁、轻量级锁的引入),其性能表现已大幅提升,在很多场景下甚至优于ReentrantLock

同步锁的三大核心特性

  • 互斥性:确保同一时刻只有一个线程获取锁并执行同步代码

  • 可见性:保证释放锁时对共享变量的修改能立即被其他线程看见

  • 有序性:禁止指令重排序,确保同步代码块内的操作按顺序执行

这些特性通过JVM底层的监视器锁(Monitor)机制实现,是理解Synchronized的关键切入点。

锁的状态与类型:从无锁到重量级锁的演进

JVM视角下的锁状态体系

从JVM实现层面看,锁的状态可分为4种,每种状态对应不同的竞争程度和性能特征:

状态值状态名称竞争程度典型场景Mark Word结构(64位JVM)
0无锁状态无竞争单线程访问对象哈希码(25bit) + GC分代年龄(4bit) + 锁标志位(01) + 偏向锁标志(0)
1偏向锁状态轻微竞争单线程重复访问线程ID(54bit) + GC分代年龄(4bit) + 锁标志位(01) + 偏向锁标志(1)
2轻量级锁状态中度竞争多线程自旋等待指向栈中锁记录的指针(62bit) + 锁标志位(00)
3重量级锁状态高度竞争多线程阻塞等待指向监视器对象的指针(62bit) + 锁标志位(10)

锁状态转换图

无锁状态 ↔ 偏向锁状态 ↔ 轻量级锁状态 ↔ 重量级锁状态↑                                 ↓└────────────── 锁膨胀 ────────────┘

这种状态转换是单向不可逆的,只能从低竞争状态向高竞争状态升级(锁膨胀),而不能降级,这是JVM为了优化性能做出的设计选择。

偏向锁:单线程优化的利器

偏向锁的核心思想

偏向锁是JVM对"同一线程多次获取同一锁"场景的优化,它通过在对象头中记录线程ID的方式,避免重复获取锁的开销。当一个线程首次获取对象锁时,JVM会将偏向锁标志位设为1,并将线程ID写入Mark Word,后续该线程再次访问时无需进行CAS操作,直接判断Mark Word中的线程ID是否与当前线程一致。

偏向锁的激活与撤销

  • 激活条件:JVM参数-XX:+UseBiasedLocking(JDK6后默认启用)

  • 撤销场景

    • 当其他线程尝试获取偏向锁时,会触发偏向锁撤销

    • 调用wait()/notify()等方法时,偏向锁会升级为轻量级锁

    • 偏向锁可以通过BiasedLockingStartupDelay参数控制延迟激活时间

典型应用场景

偏向锁最适合"单线程反复访问同步资源"的场景,例如:

public class BiasedLockDemo {private Object lock = new Object();public void doWork() {synchronized (lock) {// 单线程频繁执行的业务逻辑}}
}

在这种场景下,偏向锁能消除几乎所有的锁获取开销。

轻量级锁:自旋等待的艺术

轻量级锁的实现原理

当偏向锁被撤销或遇到轻度竞争时,锁会升级为轻量级锁。其核心原理是通过CAS操作在栈帧中创建"锁记录"(Lock Record),并将对象头的Mark Word替换为指向锁记录的指针:

  1. 线程在栈中创建Lock Record,复制对象头Mark Word到Lock Record(Displaced Mark Word)

  2. 尝试用CAS将对象头Mark Word替换为指向Lock Record的指针

  3. CAS成功则获取锁,失败则进入自旋等待

  4. 自旋一定次数后仍未获取锁,则升级为重量级锁

自旋优化的权衡

自旋等待(Spin Waiting)是指线程不放弃CPU,而是循环检查锁是否可用。这种方式避免了线程阻塞的开销,但会消耗CPU资源。JVM通过-XX:PreBlockSpin参数控制自旋次数,默认值为10次。在多核CPU环境下,自旋优化能显著提升轻度竞争场景的性能。

轻量级锁与偏向锁的对比

特性偏向锁轻量级锁
竞争程度无竞争轻度竞争
加锁方式CAS记录线程IDCAS修改对象头指针
解锁开销几乎无需CAS还原Mark Word
典型场景单线程反复访问多线程交替访问

重量级锁:操作系统级别的同步

重量级锁的底层实现

当轻量级锁自旋超过阈值或竞争更加激烈时,锁会膨胀为重量级锁。此时JVM会调用操作系统的互斥量(Mutex)来实现线程阻塞,具体过程包括:

  1. 创建与对象关联的监视器(Monitor)对象

  2. 线程进入监视器的等待队列,状态变为BLOCKED

  3. 释放CPU资源,等待操作系统调度唤醒

  4. 唤醒后重新尝试获取锁

重量级锁的性能开销

重量级锁的性能开销主要来自:

  • 线程状态切换(用户态→内核态→用户态)

  • 操作系统调度器的上下文切换

  • 等待队列的管理开销

在JDK6之前,Synchronized默认使用重量级锁,这也是其"性能不佳"印象的来源。但经过锁升级优化后,重量级锁的使用场景已大幅减少。

锁膨胀的触发条件

锁状态从低到高升级的关键触发条件包括:

  • 偏向锁遇到其他线程竞争

  • 轻量级锁自旋次数超过阈值(默认10次)

  • 调用Object.wait()等会导致线程阻塞的方法

  • 锁竞争持续时间超过自旋优化的收益临界点

Synchronized与Java内存模型(JMM)的深层联系

JMM的核心架构

Java内存模型定义了线程和主内存之间的抽象关系:

  • 主内存:所有线程共享的内存区域,存储共享变量

  • 工作内存:每个线程私有的内存区域,存储共享变量的副本

这种架构导致了一个核心问题:线程间如何保证共享变量的可见性?Synchronized通过以下机制解决这一问题:

Synchronized的内存语义

当线程执行synchronized同步块时,会遵循以下内存规则:

  1. 进入同步块

    • 从主内存读取共享变量的最新值到工作内存

    • 清空工作内存中与同步块相关的变量副本

    • 保证同步块内操作的有序性(禁止指令重排序)

  2. 退出同步块

    • 将工作内存中的变量修改刷新到主内存

    • 确保所有对共享变量的修改对其他线程可见

    • 建立happens-before关系,保证后续线程能看到最新数据

这种机制通过JVM在编译时生成的monitorentermonitorexit指令实现,确保了同步操作的可见性和有序性。

happens-before原则与Synchronized

JMM中的happens-before原则定义了操作之间的偏序关系,其中与Synchronized相关的规则包括:

  • 监视器锁规则:对一个锁的解锁操作happens-before于后续对该锁的加锁操作

  • 程序顺序规则:同步块内的操作按程序顺序执行

  • 传递性:若A happens-before B且B happens-before C,则A happens-before C

这些规则共同保证了Synchronized同步块内操作的正确性,例如:

private int x = 0;
private Object lock = new Object();
​
public void update() {synchronized (lock) {x = 1; // 操作1x = 2; // 操作2} // 解锁操作,happens-before后续加锁操作
}
​
public void read() {synchronized (lock) { // 加锁操作,happens-after解锁操作assert x == 2; // 一定成立}
}

由于解锁操作happens-before加锁操作,读操作必然能看到写操作的最新结果。

其他同步解决方案:与Synchronized的对比与互补

ReentrantLock:灵活的显式锁

ReentrantLock的核心特性

ReentrantLock(可重入锁)是JUC包中提供的同步工具,与Synchronized相比具有以下优势:

  • 显式锁控制:通过lock()unlock()方法显式获取和释放锁

  • 可中断获取锁:支持lockInterruptibly()方法,可响应中断

  • 公平锁机制:支持公平锁模式,保证线程获取锁的顺序

  • 条件变量:通过newCondition()方法创建条件变量,实现更灵活的等待/通知机制

公平锁与非公平锁的实现差异

ReentrantLock支持两种锁模式:

  • 非公平锁(默认):新线程可能在等待队列头部线程之前获取锁,性能更高

  • 公平锁:严格按照线程等待顺序获取锁,避免饥饿

// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
​
// 使用示例
fairLock.lock();
try {// 同步代码块
} finally {fairLock.unlock();
}

公平锁通过AQS(AbstractQueuedSynchronizer)的等待队列实现,而非公平锁在获取锁时会先尝试直接获取,可能跳过等待队列中的线程。

ReentrantLock与Synchronized的对比

特性SynchronizedReentrantLock
锁获取方式隐式(自动加锁/解锁)显式(手动调用方法)
可重入性支持支持
公平性非公平可选择公平/非公平
锁中断不支持支持
条件变量不支持支持
性能(无竞争)优(偏向锁优化)略逊
性能(高竞争)略逊优(可中断、公平锁)

ReadWriteLock:读写分离的同步策略

读写锁的核心思想

ReadWriteLock(读写锁)将锁分为读锁和写锁,允许多个线程同时获取读锁,但同一时刻只能有一个线程获取写锁。这种设计特别适合"读多写少"的场景,例如缓存系统:

public class Cache {private final ReadWriteLock lock = new ReentrantReadWriteLock();private Map<String, Object> data = new HashMap<>();// 读操作获取读锁public Object get(String key) {lock.readLock().lock();try {return data.get(key);} finally {lock.readLock().unlock();}}// 写操作获取写锁public void put(String key, Object value) {lock.writeLock().lock();try {data.put(key, value);} finally {lock.writeLock().unlock();}}
}

读写锁的状态管理

ReentrantReadWriteLock通过一个整数(32位)来管理两种锁状态:

  • 高16位:记录读锁的获取次数(可被多个线程共享)

  • 低16位:记录写锁的获取次数(仅能被一个线程持有)

这种设计使得读写锁能在一个变量中维护两种锁状态,提高了空间效率。

读写锁的适用场景

读写锁适合以下场景:

  • 读取操作频率远高于写入操作

  • 写入操作耗时较短

  • 需要保证读取操作的一致性

例如:

  • 配置文件读取(很少修改,频繁读取)

  • 缓存系统(读多写少)

  • 数据库查询缓存(查询频繁,更新较少)

但需注意,读写锁在写操作频繁的场景下性能可能不如普通互斥锁,因为读锁的释放可能导致写锁饥饿。

Synchronized在JDK源码中的典型应用

容器类中的同步实现

StringBuffer的同步实现

StringBuffer是JDK中典型的线程安全容器,其所有关键方法都使用Synchronized修饰:

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {// 构造函数public StringBuffer() {super(16);}// 同步追加方法public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;}// 同步插入方法public synchronized StringBuffer insert(int offset, char str[]) {toStringCache = null;super.insert(offset, str);return this;}// 其他同步方法...
}

这种实现方式保证了StringBuffer在多线程环境下的安全性,但也意味着所有操作都需要获取锁,在高并发场景下可能成为性能瓶颈。

Vector的同步机制

Vector与ArrayList功能相似,但所有操作都是同步的:

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {// 同步添加元素public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;}// 同步获取元素public synchronized E get(int index) {if (index >= elementCount)throw new ArrayIndexOutOfBoundsException(index);return elementData(index);}// 其他同步方法...
}

与StringBuffer类似,Vector的同步实现保证了线程安全,但在并发环境下性能不如非同步容器。JDK推荐在非必要时使用ArrayList,仅在需要线程安全时通过Collections.synchronizedList(new ArrayList<>())包装。

基础类库中的同步应用

Hashtable的同步实现

Hashtable是Java早期的线程安全哈希表,其实现方式与Vector类似:

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {// 同步put方法public synchronized V put(K key, V value) {// 检查key是否为nullif (key == null) {throw new NullPointerException();}V oldValue = get(key);putVal(key, value, false);return oldValue;}// 同步get方法public synchronized V get(Object key) {if (key == null) {throw new NullPointerException();}Entry<?,?> e = getEntry(key);return (e == null) ? null : (V)e.value;}// 其他同步方法...
}

由于Hashtable的同步粒度较大(整个哈希表),在高并发场景下性能较差,因此JDK后来提供了ConcurrentHashMap作为替代方案,其采用分段锁机制大幅提升了并发性能。

自定义同步工具的基础

Synchronized也是JDK中许多自定义同步工具的实现基础,例如:

public class Semaphore {// 内部通过AQS实现,但AQS的底层操作依赖于CAS和Monitorpublic Semaphore(int permits) {sync = new NonfairSync(permits);}// 其他方法...
}

虽然Semaphore的上层接口不直接使用Synchronized,但底层AQS的实现仍然依赖于JVM的监视器锁机制,体现了Synchronized在JDK中的基础地位。

Synchronized的最佳实践与性能优化

精细化同步范围

最小化同步代码块

// 反例:同步范围过大
public void badPractice() {synchronized (this) {// 非共享资源操作,无需同步loadConfig();// 共享资源操作,需要同步updateSharedData();// 非共享资源操作,无需同步logOperation();}
}// 正例:缩小同步范围
public void goodPractice() {// 非共享资源操作loadConfig();// 仅同步必要的代码块synchronized (this) {updateSharedData();}// 非共享资源操作logOperation();
}

通过缩小同步范围,可以减少线程竞争,提高并发性。基本原则是:只对真正访问共享资源的代码加锁

同步对象的选择

优先使用私有锁对象

private final Object lock = new Object();public void operation() {synchronized (lock) {// 同步代码}
}

私有锁对象避免了外部代码直接访问锁,减少了锁竞争的意外风险。

避免使用this作为锁对象

public void badLock() {synchronized (this) { // 危险!外部可能获取this锁导致死锁// 同步代码}
}

除非类本身设计为线程安全的同步组件,否则应避免使用this作为锁对象。

利用锁的可重入性

可重入性的实际应用

public class ReentrantDemo {public synchronized void method1() {System.out.println("进入method1");method2(); // 调用同步方法method2System.out.println("退出method1");}public synchronized void method2() {System.out.println("进入method2");// 其他操作System.out.println("退出method2");}
}

在上述示例中,method1调用method2时,由于Synchronized锁的可重入性,线程无需再次获取锁,避免了死锁风险。可重入性是通过锁的计数器实现的,每次进入同步块时计数器加1,退出时减1,当计数器为0时才真正释放锁。

可重入性与继承场景

class Parent {public synchronized void operation() {System.out.println("Parent operation");}
}class Child extends Parent {@Overridepublic synchronized void operation() {System.out.println("Child before");super.operation(); // 调用父类同步方法System.out.println("Child after");}
}

在继承场景下,子类重写同步方法并调用父类方法时,可重入性保证了锁的正确获取,避免了因多次加锁导致的死锁。

性能优化参数调整

偏向锁相关参数

启用/禁用偏向锁

-XX:+UseBiasedLocking  // 启用偏向锁(JDK6后默认启用)
-XX:-UseBiasedLocking // 禁用偏向锁

偏向锁延迟激活

-XX:BiasedLockingStartupDelay=0 // 启动时立即激活偏向锁(默认延迟4秒)

轻量级锁自旋参数

自旋次数设置

-XX:PreBlockSpin=20 // 设置自旋次数(默认10次)

自适应自旋

-XX:+UseAdaptiveSpinning // 启用自适应自旋(JDK6后默认启用)

自适应自旋会根据前一次自旋的成功情况动态调整自旋次数,提高优化效果。

场景化选择策略

选择Synchronized的场景

  • 简单同步需求:无需复杂锁控制的场景

  • 单线程或低竞争环境:偏向锁和轻量级锁能发挥最佳性能

  • 代码简洁性优先:隐式加锁/解锁减少代码量

  • 与JMM结合的场景:需要利用Synchronized的内存语义保证可见性

选择ReentrantLock的场景

  • 需要公平锁机制:避免线程饥饿

  • 需要可中断锁:响应线程中断

  • 需要条件变量:实现更灵活的等待/通知机制

  • 高竞争环境:ReentrantLock的性能可能更优

  • 需要手动控制锁释放:如配合try-finally确保解锁

选择ReadWriteLock的场景

  • 读多写少的场景:如缓存、配置文件等

  • 需要读写分离:提高读操作的并发性

  • 写入操作耗时较短:避免读锁饥饿

总结

从"重量级锁"到"智能锁"的进化

回顾Synchronized的发展历程,我们可以看到JVM团队在性能优化上的持续努力:

  1. JDK1.0-1.5:仅支持重量级锁,性能较差

  2. JDK6:引入偏向锁、轻量级锁,大幅提升性能

  3. JDK7:优化锁膨胀路径,减少重量级锁的使用

  4. JDK8+:进一步优化偏向锁的获取和撤销流程

这种进化使得Synchronized在无竞争和轻度竞争场景下的性能接近无锁操作,重新成为Java并发编程的首选同步工具之一。

同步机制的选择原则

在实际开发中,选择同步机制应遵循以下原则:

  1. 优先使用Synchronized:对于大多数场景,Synchronized已足够高效,且代码更简洁

  2. ReentrantLock作为补充:当需要公平锁、可中断锁或条件变量时使用

  3. ReadWriteLock谨慎使用:仅在读多写少场景下使用,避免写锁饥饿

  4. 性能测试验证:不同场景下锁的性能表现可能不同,需通过实测确定最优方案

核心知识回顾

  • 锁状态体系:无锁→偏向锁→轻量级锁→重量级锁的状态转换

  • 实现原理:基于JVM监视器锁,通过Mark Word记录锁状态

  • 内存语义:保证共享变量的可见性和操作的有序性

  • 性能优化:偏向锁、轻量级锁、自旋等待等优化手段

  • 应用场景:结合具体业务需求选择合适的同步方案

掌握Synchronized的原理与应用,是成为Java并发编程高手的必经之路。通过深入理解其底层实现和优化机制,我们能够更精准地运用这一强大工具,构建高效、安全的多线程应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85977.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85977.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/85977.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

跟着AI学习C# Day26

&#x1f4c5; Day 26&#xff1a;C# 异步编程进阶 ✅ 学习目标&#xff1a; 深入理解 async/await 的底层机制&#xff1b;掌握 ConfigureAwait(false) 的作用与使用场景&#xff1b;避免异步死锁&#xff0c;理解同步上下文&#xff08;Synchronization Context&#xff09…

Scrapy | 通过爬取豆瓣Top250电影信息来学习在中间件中应用随机请求头和代理ip

中间件的使用 1.scrapyl中间件的分类和作用1.1 scrapy中间件的分类1.2 scrapy中间的作用:预处理request和response对象2.下载中间件的使用方法:3.定义实现随机User-Agent的下载中间件3.1 实战:爬取豆瓣Top250电影信息3.2 中间件使用实现随机User-Agent4. 代理ip的使用4.1思路…

【深度学习】深度学习入门:从理论到实践的全面指南

深度学习入门&#xff1a;从理论到实践的全面指南 深度学习&#xff1a;开启人工智能新时代的钥匙一、深度学习的广泛应用场景1.1 改变生活的深度学习应用1.2 行业变革案例深度解析案例一&#xff1a;深度学习检测皮肤癌案例二&#xff1a;移动端OCR技术突破案例三&#xff1a;…

MySQL 数据库操作完整指南

MySQL 数据库操作完整指南 目录 创建数据库 连接数据库 创建表 约束详解 插入数据 查询数据 多表联合查询 连接查询 高级查询 更新数据 删除数据 视图详解 存储过程详解 函数详解 触发器 事务处理 索引优化 安全性管理 备份和恢复 性能优化 删除表和数据库 1. 创建数据库 基…

Java面试复习:面向对象编程、Java 8新特性与Spring

Java面试复习&#xff1a;面向对象编程、Java 8新特性与Spring 面向对象编程 概念解析&#xff1a;面向对象编程&#xff08;OOP&#xff09;是将现实世界中的概念抽象为软件模型的编程范式&#xff0c;包括封装、继承和多态。 核心原理&#xff1a; 封装&#xff1a;通过访…

蓝牙数据通讯,实现内网电脑访问外网电脑

最近突然想到了一个可以绕开单位安全管控软件&#xff0c;让单位内部办公电脑连上外网的方法。大概是这个样子&#xff0c;让单位办公电脑与自己的外网电脑进行蓝牙配对&#xff0c;然后用配对成功的蓝牙进行网络数据交互。这里大家可能会想用一下蓝牙的网络共享功能&#xff0…

硬件面经-具身机器人通用技术要求

目录 简介 场景 技术面试 设计知识点 总结 简介 最近机器人特别的火。所以收集了一些关于机器人的面试及要求 场景 目前具身机器人赛道可谓是十分火热,全国大大小小崛起了几十家具身机器人公司,国外比较出名的有波士顿动力,特斯拉等,国内目前比较火的就是宇树,众擎…

DeepSeek生成HTML5图片拼接工具

让DeepSeek生成一个HTML5图片拼接工具&#xff0c;给的提示词如下 提示词(prompt) 帮我生成一个可以将两张图片拼接到一起的程序 支持横向拼接和竖向拼接&#xff0c;可以用html5实现功能吗&#xff1f; DeepSeek大概20秒左右就做好了&#xff0c;而且像这么简单的功能的话也没…

Java面试复习指南:Java基础、面向对象编程与并发编程

Java面试复习指南&#xff1a;Java基础、面向对象编程与并发编程 1. Java基础 概念解析: Java是一种面向对象的编程语言&#xff0c;具有跨平台的特性。 核心原理: JVM负责Java程序的跨平台运行&#xff0c;通过字节码来实现。 高频面试问题: Java如何实现跨平台&#xff…

LeeCode2566替换一个数字后的最大差值

项目场景&#xff1a; 给你一个整数 num 。你知道 Danny Mittal 会偷偷将 0 到 9 中的一个数字 替换 成另一个数字。 请你返回将 num 中 恰好一个 数字进行替换后&#xff0c;得到的最大值和最小值的差为多少。 注意&#xff1a; 当 Danny 将一个数字 d1 替换成另一个数字 …

李宏毅2025《机器学习》第三讲-AI的脑科学

在之前的课程中探讨了如何利用大模型构建AI Agent,本科将暂时放下应用层面的探索,拿起“手术刀”和“显微镜”,深入剖析LLM的“大脑”,带您踏上一场“AI脑神经科学”的旅程。课程不讨论模型的训练过程,而是假设我们拥有一个已经训练好的、功能完备的LLM。我们的目标是:理…

CVPR 2025 | 微米级光影CT精度!复旦腾讯优图开源Real-IAD D³数据集

【导读】 本文介绍了复旦联合腾讯优图发布高精度多模态数据集Real-IAD D&#xff0c;并基于此数据集提出了一种创新的多模态融合检测方法&#xff0c;数据集已被CVPR 2025收录&#xff0c;并开源。>>更多资讯可加入CV技术群获取了解哦~ 目录 一、Real-IAD D的创新之处…

解决mysql左连接加where就不会保留左表中的全部数据的问题

在SQL中使用MySQL的LEFT JOIN操作时,如果加入了WHERE条件,确实会影响结果集的完整性,特别是如果你在WHERE条件中使用了JOIN的另一张表中的字段作为过滤条件。这是因为当你在WHERE子句中加入了对JOIN另一张表的过滤条件时,实际上你是在执行一个INNER JOIN(内连接)而非LEFT…

算法与数据结构:动态规划DP

文章目录 动态规划算法全面解析一、核心思想与基本概念二、动态规划与其他算法的区别三、动态规划的解题步骤四、经典案例解析1. **斐波那契数列&#xff08;Fibonacci&#xff09;**2. **0-1背包问题&#xff08;0-1 Knapsack&#xff09;**3. **最长公共子序列&#xff08;LC…

Coilcraft电感上的横线是什么意思?电感有方向么?

通常我们会认为电容、电感、电阻这几类无源器件没有方向性&#xff0c;在布局和贴片时可以任意方向放置&#xff0c;也不会在PCB上增加丝印标识说明其方向。与此相互印证的是&#xff0c;电容表面无丝印&#xff0c;无法识别方向&#xff1b;电阻表面一般只有包含阻值大小的数字…

通过Docker挂载nginx并修改页面

1&#xff1a;通过docker创建nginx&#xff1a; 首先关闭原来的Docker&#xff08;防止端口号冲突&#xff09; sudo nginx -s stop 直接启动 Nginx 进程 sudo nginx 启动nginx&#xff1a; docker run -di --namemynginx -p 80:80 nginx cd /etc/nginx docker run -d …

力扣1124. 表现良好的最长时间段

这一题我看到数据范围是10^4&#xff0c;暗自窃喜能用双重循环&#xff0c;看题目是典型的前缀和哈希。不过需要一个转换将大于8小时的转化为1&#xff0c;其他都为-1&#xff0c;方便计算&#xff0c;之前的题目中也有这种方法。 那这样就简单了 class Solution { public:int…

EDA2算法速通(编者崩溃版)

这个内容是用来回忆一下EDA2涉及的算法和解题的主要步骤&#xff1a; 有疑问或发现错误可以私信来讨论 高级综合概述 柏拉图优化&#xff1a;这个是来判断是否有哪些节点能完全被其他节点优化掉。比如&#xff08;1,2&#xff09;这个节点就可以完全优化&#xff08;3,4&…

雷池waf配置第三方登录-钉钉配置详细教程

雷池waf配置第三方登录-钉钉配置详细教程 前往钉钉开放平台https://open.dingtalk.com/ 选择一个登录方式登录钉钉开放平台 选择一个自己所管理的组织 登录成功后点击我的后台 选择应用开发 在钉钉应用下点击创建应用 填写应用名称和应用描述后点击保存 点击网页…

神经网络中的均方误差(Mean Squared Error)详解

引言 在机器学习和神经网络领域&#xff0c;损失函数&#xff08;Loss Function&#xff09;是衡量模型预测值与真实值之间差异的关键指标。均方误差&#xff08;Mean Squared Error, MSE&#xff09;作为一种经典的损失函数&#xff0c;因其简单性、可解释性和数学上的优良性…