解密Java中神奇的Synchronized关键字

news2025/2/26 2:51:47

文章目录

      • 🎉 定义
      • 🎉 JDK6以前
      • 🎉 偏向锁和轻量级锁
        • 📝 偏向锁
        • 📝 轻量级锁
        • 📝 自旋锁
        • 📝 重量级锁
          • 🔥 1. 加锁
          • 🔥 2. 等待
          • 🔥 3. 撤销
      • 🎉 锁优化
        • 📝 锁消除
        • 📝 锁粗化
        • 📝 自适应自旋
      • 🎉 synchronized关键字的用法和注意事项
        • 📝 修饰方法
        • 📝 修饰代码块
        • 📝 修饰静态方法
        • 📝 修饰类

📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。

📙经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

以梦为马,不负韶华

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

  • 💂 博客主页: 我是廖志伟
  • 👉开源项目:java_wxid
  • 🌥 哔哩哔哩:我是廖志伟
  • 🎏个人社区:幕后大佬
  • 🔖个人微信号SeniorRD

💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

CSDN

解密Java中神奇的Synchronized关键字

在Java中,当多个线程同时访问同一块代码,会产生竞态条件,可能会导致数据不一致或其他问题。为了解决这个问题,Java提供了synchronized关键字,它能够保证同一时刻被synchronized修饰的代码最多只有1个线程执行。本文将从synchronized的定义、JDK6以前的实现方式、偏向锁和轻量级锁、锁优化、synchronized关键字的用法和注意事项等方面详细讲解。

🎉 定义

在Java中,synchronized关键字是一种同步锁,在多线程编程中,用于解决多个线程同时访问同一个资源的问题。当一个线程持有锁时,其他线程将会被阻塞,直到当前线程释放锁为止。synchronized可以加在方法上或对象上,作用的对象是非静态的,则取得的锁是对象锁;作用的对象是静态方法或类,则取到的锁是类锁,这个类所有的对象用的是同一把锁。

🎉 JDK6以前

在JDK6以前,synchronized加锁是通过对象内部的监视器锁来实现的,监视器锁本质上又是依赖于底层的操作系统的Mutex Lock来实现的,因此在高并发情况下,synchronized的性能就会变得非常低下。

当多个线程争夺同一个锁时,会发生线程阻塞和唤醒的操作,这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要比较长的时间,导致程序执行效率低下。

🎉 偏向锁和轻量级锁

为了提高synchronized的效率,JDK6引入了偏向锁和轻量级锁。

📝 偏向锁

在Java中,同步操作需要获取锁来保证多个线程访问共享资源的安全性。而在锁的获取过程中,JVM添加了一种优化方式——偏向锁。当一个线程访问同步块时,如果这个同步块没有被其他线程占用,那么JVM会把这个同步块的锁标记为偏向锁,并把当前线程ID记录在MarkWord中。这样,下次同一线程访问同步块时,就不需要再次获取锁,直接进入同步块即可。

但是,当有其他线程来竞争锁的时候,偏向锁需要进行撤销,转而使用轻量级锁或者重量级锁来保证同步操作的安全性。这里给出一个对象从无锁到偏向锁转化的过程:

第一步,检测MarkWord是否为可偏向状态,即锁标识位是01,如果是,则说明当前对象可被偏向,可以执行同步代码块。

第二步,如果需要访问同步块的线程ID与MarkWord中记录的线程ID相同,则不需要进行竞争,直接执行同步代码块即可。

第三步,如果需要访问同步块的线程ID与MarkWord中记录的线程ID不同,说明该对象已经被其他线程占用,需要进行竞争。此时,JVM会进行CAS操作,如果操作成功,则将线程ID替换为当前线程ID,并执行同步代码块。

第四步,如果CAS操作失败,则需要进行偏向锁的撤销。这个过程可以有两种情况触发:一是对象头的Epoch字段计数到一定次数,二是多个线程尝试竞争该对象的锁,都失败了。

第五步,完成偏向锁的撤销后,持有偏向锁的线程不会被挂起,继续执行同步代码块。如果获取锁失败,则视情况进行自旋或者进行阻塞等待,进一步升级为轻量级锁或重量级锁。

需要注意的是,在偏向锁撤销的过程中,需要清除那些曾经持有该偏向锁对象的线程的锁记录。这是因为在偏向锁状态下,持有锁的线程会在对象头中记录一个标记位和持有该锁的线程ID。而在撤销偏向锁的过程中,需要清除这些锁记录,因为它们已经不再持有该锁,以便其他线程可以重新争夺锁的所有权。并且,偏向锁撤销的过程不一定会挂起所有持有偏向锁的线程,只有在线程竞争锁时才会挂起线程。

在偏向锁撤销过程中,JVM会启动偏向锁撤销线程来遍历所有持有该偏向锁对象的线程栈,清除它们的锁记录。而在多线程编程中,当多个线程对一个内存位置进行读取和修改时,可能会出现一种情况——ABA问题。为了解决这个问题,JVM 在对象的内存布局中添加了一个Epoch字段,来判断一个线程是否因为ABA问题导致的线程变化。这个Epoch字段并不直接关系到偏向锁撤销的过程,但是有助于判断锁的状态是否发生了变化。

在实际的应用中,偏向锁的优化方式能够显著提高同步代码块的性能,但它并不适用于所有场景。在多线程应用程序中,如果存在大量的锁竞争,那么偏向锁的优化效果会下降,甚至被轻量级锁或重量级锁取代。因此,在使用偏向锁的时候,需要根据具体情况进行考虑和使用。同时,了解偏向锁的撤销过程,有助于我们更好地理解同步机制的底层实现,更好地进行多线程编程。

📝 轻量级锁

轻量级锁升级过程是为了实现对象的互斥访问,首先在当前线程的栈帧中创建一个锁记录用于存储锁对象的MarkWord的拷贝。该拷贝无锁状态对象头中的MarkWord,用于在申请对象锁时作为CAS的比较条件。同时也能通过这个比较判定是否在持有锁的过程中,这个锁被其他线程申请过,如果有,在释放锁的时候要唤醒被挂起的线程。轻量级锁的MarkWord如果存有hashCode,解锁后也需要恢复。拷贝成功后,虚拟机使用CAS操作把对象中对象头的MarkWord替换为指向锁记录的指针,再把锁记录空间里的owner指针指向加锁的对象。如果这个更新操作成功,那么当前线程就拥有了该对象的锁,并且对象MarkWord的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。

如果更新操作失败,虚拟机会检查对象的MarkWord中的Lock Word是否指向当前线程的栈帧。如果是,则当前线程已经拥有了这个对象的锁,可以直接进入同步块继续执行。如果不是,则说明多个线程竞争锁,要进入自旋。若自旋结束时依然未获得锁,轻量级锁就要升级为重量级锁,锁标志的状态值变为“10”,对象MarkWord中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后续等待锁的线程也要进入阻塞状态。

当锁升级为轻量级锁后,如果依然有新线程过来竞争锁,首先新线程会自旋尝试获取锁,尝试到一定次数(默认10次)依然没有拿到,锁就会升级成重量级锁。一般来说,同步代码块内的代码应该很快就执行结束,这时线程B自旋一段时间是很容易拿到锁的。但如果不巧,没能拿到锁,自旋就会成为死循环,并且耗费CPU。因此,虚拟机会直接将锁升级为重量级锁,不再进行自旋。这样就不需要了线程一直自旋,性能会得到很大的提升。

📝 自旋锁

自旋锁并不是一种锁状态,而是一种智能的线程同步策略。它可以用于保护临界区的并发访问,避免多个线程同时进入临界区导致的数据不一致问题。自旋锁的核心思想是,在等待锁的过程中,不会主动阻塞线程,而是继续执行当前线程内的代码,同时不断自旋等待锁的释放,以减少线程的阻塞和唤醒,提高并发性能。

当一个线程尝试获取某个锁时,如果发现该锁已经被其他线程占用,则该线程不会被立即挂起或者睡眠,而是开始自旋等待锁的释放。自旋等待期间,该线程会一直占用CPU处理器资源,循环检测锁是否被释放。自旋等待不能替代阻塞,因为如果自旋时间过长,会占用过多CPU资源,反而降低性能。

自旋锁适用于维护临界区很小的情况。临界区很小表示锁占用的时间很短,如果持有锁的线程很快就能释放锁,那么自旋的效率就会非常高。但是自旋的次数必须要有一个限度,如果自旋超过了定义的次数仍然没有获取到锁,就应该被挂起。然而这个限度不能固定,因为程序锁的状况是不可预估的,所以JDK1.6引入了自适应的自旋锁,可以根据程序运行的情况动态调整自旋的次数。比如如果线程自旋成功了,那么下次自旋的次数会更多,反之则会更少,从而避免了自旋等待过程中浪费处理器资源的情况。

要开启自旋锁,可以使用JDK1.6之后提供的参数–XX:+UseSpinning,如果需要修改自旋次数,可以使用–XX:PreBlockSpin参数来指定,其中默认值为10次。使用自旋锁可以在一定程度上提高多线程程序的性能,但也需要注意合理设置自旋次数和使用范围,以免造成过多的CPU资源占用和线程的饥饿等问题。

📝 重量级锁

重量级锁是Java中的一种锁,是通过对象内部的监视器锁(Monitor)来实现的。监视器锁本质上又是依赖于底层的操作系统的MutexLock来实现的。由于操作系统实现线程之间的切换需要从用户态转换到核心态,状态之间的转换需要比较长的时间,因此依赖于操作系统MutexLock所实现的锁我们称之为“重量级锁”。

当一个线程在等待锁时,会不停的进行自旋,其中自旋的线程数达到CPU核数一半之后,就会升级为重量级锁。在升级为重量级锁之后,锁标志会被置为10,同时将MarkWord中的指针指向重量级的Monitor,这将阻塞所有没有获取到锁的线程。

重量级锁的加锁-等待-撤销流程分为三个步骤。

🔥 1. 加锁

当一个线程请求锁时,首先会查看锁标志是否为0,如果为0,则表示锁没有被占用,此时该线程会将锁标志置为1,并且获取到锁。如果锁标志不为0,则表示锁已经被占用,此时该线程会进入自旋状态。

🔥 2. 等待

在自旋状态下,如果锁一直没有被释放,那么自旋的线程数量会不断增加。当自旋的线程数量达到CPU核数的1/2时,就会升级为重量级锁。在升级为重量级锁之后,会将锁标志置为10,同时将MarkWord中的指针指向重量级的Monitor,这将阻塞所有没有获取到锁的线程。

🔥 3. 撤销

当一个线程释放锁时,会将锁标志置为0。此时正在等待的线程会被唤醒并争夺锁,曾经获得过锁的线程,在被唤醒之后会优先得到锁。如果一个线程在等待锁的过程中调用了wait()方法,则该线程会被加入到等待队列中,并通过wait_set等待被唤醒。如果一个线程在等待锁的过程中调用了notify()方法,则该线程会将等待队列中的第一个线程唤醒,等待队列中被唤醒的线程会被加入到同步队列中,并通过park()方法等待获取锁。

当重量级锁撤销之后,系统会将其转变为无锁状态。撤销锁之后会清除创建的Monitor对象,并修改markword,这个过程需要一段时间。Monitor对象是通过GC来清除的。当GC清除掉Monitor对象之后,锁就被撤销为无锁状态。

重量级锁适用于多线程互斥访问同一资源的情况。由于重量级锁性能较低,因此只在必要时才应该使用。在Java中,Synchronized关键字就是一种重量级锁。在使用Synchronized关键字时,应尽量减少锁的持有时间,这样可以提高程序的并发性能。

🎉 锁优化

针对于synchronized的性能问题和在某些情况下可能导致死锁的情况,Java提供了以下的锁优化:

📝 锁消除

在编译器层面消除不必要的加锁操作,将锁的范围缩小到最小,这样可以减少锁竞争的概率,提高程序的执行效率。

📝 锁粗化

在进行一系列操作时,将多个连续的加锁操作放在一个代码块中,这样可以减少加锁和解锁的开销,提高程序的执行效率。

📝 自适应自旋

当线程尝试获取同步锁失败后,它并不会立即进入阻塞状态,而是再次尝试获取同步锁,如果一段时间内失败的次数越多,就会进入阻塞状态。这个时间段就是自旋时间,是由操作系统动态调整的。

🎉 synchronized关键字的用法和注意事项

synchronized关键字的用法有以下几种:

📝 修饰方法

这种方式是修饰整个方法,即使方法中没有同步代码块,也会锁定这个方法,这种方式适用于整个方法需要同步的情况。

public synchronized void method() {
    // 同步代码块
}
📝 修饰代码块

这种方式是将同步代码块包在synchronized括号内,只有在执行到synchronized代码块时才会锁定,这种方式适用于只需要同步执行部分代码的情况。

public void method() {
    synchronized (this) {
        // 同步代码块
    }
}
📝 修饰静态方法

和修饰方法类似,这种方式是锁定整个静态方法,适用于整个静态方法需要同步的情况。

public synchronized static void method() {
    // 同步代码块
}
📝 修饰类

这种方式是锁定整个类,即使不同实例中的线程也会被锁定,适用于整个类需要同步的情况。

public void method() {
    synchronized (ClassName.class) {
        // 同步代码块
    }
}

需要注意的是,只有在多个线程访问同一块资源时,才需要使用synchronized关键字。如果同步块内的代码很少,那么锁的代价就会超过同步块内的代码的执行代价,从而导致程序执行效率变低。同时,在使用synchronized关键字时,需要考虑死锁问题,即多个线程无限制地等待对方释放锁的情况。因此,在编写代码时,需要特别注意同步块的范围和锁的粒度。

CSDN

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

  • 💂 博客主页: 我是廖志伟
  • 👉开源项目:java_wxid
  • 🌥 哔哩哔哩:我是廖志伟
  • 🎏个人社区:幕后大佬
  • 🔖个人微信号SeniorRD

📥博主的人生感悟和目标

探寻内心世界,博主分享人生感悟与未来目标

  • 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
  • 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
  • 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
  • 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将字符串中的数据按指定分隔符分割依次存入一维数组中 numpy.fromstring()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将字符串中的数据 按指定分隔符分割 依次存入一维数组中 numpy.fromstring() [太阳]选择题 请问以下代码中执行语句输出结果依次是? import numpy as np str1 "1.0 2.0 3.…

力扣第55题 跳跃游戏 c++ 贪心 + 覆盖 加暴力超时参考

题目 55. 跳跃游戏 中等 相关标签 贪心 数组 动态规划 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true &…

【算法训练-动态规划 四】【子序列类型问题】连续子数组的最大和

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【动态规划】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为&…

架构设计系列5:如何设计高可用架构

#1024程序员节|参与投稿,赢限定勋章和专属大奖# 当今的数字时代,高可用架构已经成为了现代应用和服务的基石。无论是企业级应用、云计算平台还是互联网服务,高可用性都是确保系统在面临各种挑战时保持稳定运行的关键要素。 本文…

计算机算法分析与设计(19)---回溯法(装载问题)

文章目录 1. 题目描述2. 算法思路3. 代码编写 1. 题目描述 2. 算法思路 1. 思路: 容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。 (1) 首先将第一艘轮船尽可能装满。(2) 将剩余的集装箱装上第二艘轮船。 2. 将第一…

基于springboot实现基于Java的超市进销存系统项目【项目源码+论文说明】

基于springboot实现基于Java的超市进销存系统演示 摘要 随着信息化时代的到来,管理系统都趋向于智能化、系统化,超市进销存系统也不例外,但目前国内仍都使用人工管理,市场规模越来越大,同时信息量也越来越庞大&#x…

Xilinx的FIR滤波器IP的设计与仿真

平台:Vivado2021.1 芯片:xcku115-flva1517-2-i (active) 语言:VerilogHDL 参考文件:pg149.下载地址 FIR Compiler LogiCORE IP Product Guide • FIR Compiler (PG149) • 阅读器 • AMD 自适应计算文档门户 (xilinx.com) FI…

【漏洞复现】panalog日志审计系统任意用户创建漏洞和后台命令执行

漏洞描述 panalog为北京派网软件有限公司,一款流量分析,日志分析管理的一款软件。存在任意用户创建漏洞和后台命令执行漏洞,可先通过任意用户创建,然后进行后台命令执行,获取服务器权限。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公…

怎么保护公司文件安全

怎么保护公司文件安全 无纸化办公时代,无论是企业还是个人在生活的方方面面都依赖于对电子化软件以及设备的使用。尤其是对于企业而言,在日常办公中产生的相关电子文件都是以电子文档的形式存储在电脑上。 下载试用安企神数据防泄密软件 若企业未实施…

力扣刷题 day53:10-23

1.二进制表示中质数个计算置位 给你两个整数 left 和 right ,在闭区间 [left, right] 范围内,统计并返回 计算置位位数为质数 的整数个数。 计算置位位数 就是二进制表示中 1 的个数。 例如, 21 的二进制表示 10101 有 3 个计算置位。 方…

Uncaught TypeError: Cannot use ‘in‘ operator to search for ‘path‘ in undefined

Uncaught TypeError: Cannot use ‘in’ operator to search for ‘path’ in undefined 报错如下: Uncaught TypeError: Cannot use in operator to search for path in undefinedat resolve (vue-router.esm-bundler.js:2882:13)at pushWithRedirect (vue-router…

Java将djvu文件转成pdf

需求来源 迫于有部分资源是djvu格式的文件,需要预览这部分文件,web端无法直接预览djvu,所以需要将djvu转成pdf。 转换方法 简单来说就是先把djvu文件转换成tiff文件,再将tiff文件转换成pdf文件。 Ubuntu服务器 如果服务器是U…

【软考-中级】系统集成项目管理工程师-项目收尾管理历年案例

持续更新。。。。。。。。。。。。。。。 目录 2017 下 试题三(17分)背诵整理1. 项目总结会议一般讨论的内容2. 系统文档验收所涉及的文档都有哪些 系列文章 2017 下 试题三(17分) 阅读下列说明,回答问题 1至问题 4,将解答填入答题纸的对应栏内     …

几种基本的高斯分布数学运算,加法、乘法、缩放和卷积及运用场景

文章目录 高斯分布进行乘法运算几种基本的数学运算上述这些运算可以运用与什么场景?高斯分布进行乘法运算 当两个高斯分布进行乘法运算时,这通常是在估计或滤波的上下文中,如在卡尔曼滤波中的更新步骤。为了理解背后的数学原理,我们首先定义两个高斯分布: 当我们将这两个…

107. 面试官:JS如何实现继承?

107期 1. JS如何实现继承? 2. meta标签中的viewport有什么用? 3. 说手webpack的构建流程? 上面问题的答案会在第二天的公众号(程序员每日三问)推文中公布 也可以小程序刷题,已收录500面试题及答案 106期问题及答案 1. webpack如…

华为昇腾NPU卡 ChatGLM2模型使用

参考:https://gitee.com/mindspore/mindformers/blob/dev/docs/model_cards/glm2.md#chatglm2-6b 1、安装环境: 昇腾NPU卡对应英伟达GPU卡,CANN对应CUDA底层; mindspore对应pytorch;mindformers对应transformers 本…

第1章 Java、IDEA环境部署与配置

JavaEE简介与IDE环境部署 课程目录 JavaEE简介JDK环境部署IntelliJ IDEA环境部署 JavaEE简介 1. JavaEE是什么? Java EE(Java Platform,Enterprise Edition)是sun公司(2009年4月20日甲骨文将其收购)推…

QGIS007:【01空间操作】-提取两个图层空间相交属性一致的图斑

引言:本文介绍使用QGIS图形建模器设计模型,提取出两个图层空间位置存在交集且字段(NAME;LAYER)属性值完全一致的要素。 实验数据: 链接:https://pan.baidu.com/s/1qqylXkqzRFNnKK-pRQNkMg?pwdurzx 提取码…

AlDente Pro for Mac: 掌控电池充电的终极解决方案

你是否曾经为了保护你的MacBook的电池,而苦恼于无法控制它的充电速度?AlDente Pro for Mac 是一款专为Mac用户设计的电池管理工具,它能帮助你解决这个问题。 AlDente Pro for Mac 是一款电池最大充电限制软件,它能够让你自由地设…

深度学习_4_实战_直线最优解

梯度 实战 代码: # %matplotlib inline import random import torch import matplotlib.pyplot as plt # from d21 import torch as d21def synthetic_data(w, b, num_examples):"""生成 Y XW b 噪声。"""X torch.normal(0,…