并发编程(二)—synchronized和volatile

发布时间:2026/9/8 19:33:24

并发编程(二)—synchronized和volatile
synchronized保证读和写的安全volatile保证读的安全但不保证写的安全在多处理器开发中保证了共享变量的“可见性”。可见性当一个线程 修改一个共享变量时另外一个线程能读到这个修改的值。一、高速缓存1.1 高速缓存如ALU在对数据进行for循环操作时如果在计算完成后立刻返回更新数据会出现一个运算核心在进行读写操作时一直占据总线的情况此时CPU多核处理器就失去意义所以在CPU内部设置高速缓存用于存储数据。ALU将数据存储在高速缓存中此时就释放了总线其他ALU就可以通过总线对其他数据进行操作。高速缓存的存储空间越大CPU与总线的交互就越少计算机的性能就越好。为了避免电压信号的干扰不同的指令不能同时传输即指令在总线必须排队传输内存同一时刻只能被一个指令指挥1.2 多级缓存在计算机的任务管理器中我们可以看到计算机的CPU中设置了三级缓存这是为什么多级缓存的目的也是为了加快传输速率提高每个核心的利用率。CPU从内存中读取数据一个导线同一时刻只能传输一个电压信号必须等该信号传输完成后才能继续传输下一个传输的过程对于CPU是浪费的。设置多级缓存后相当于设置了数据中转站数据到达第一个中转站后下一个数据便可发出以此进行传输提高了内存的数据发送频率与CPU的数据接收频率但是由于数据转发也需要消耗时间目前平衡性能最好的是三级缓存。二、synchronized和volatile2.1 高速缓存带来的问题通过对高速缓存的讲解我们可以看到高速缓存支持多核CPU且提高CPU的利用率但是它也带来了并发问题—相互覆盖。如上图若核心1对a进行10次的1操作计算完成后时间片到期此时核心2对a进行计算同样计算10次随后返回更新此时a10核心2任务完成后释放CPU核心1继续执行返回更新会覆盖核心2返回的数据即对a进行了20次操作但其计算得到结果为10。注高速缓存中的数据在返回更新时会将高速缓存中的该数据清空下次进行计算时需要重新从内存中读取数据。高速缓存存储容量满了之后会返回更新即更新是随机的不一定是任务执行完毕后才返回更新。2.2 volatile轻量级锁2.2.1 相关术语定义术语英文描述内存屏障memory barriers在内存中有一个变量标记该标记为某一确定值时不要继续访问。缓冲行cache line高速缓存的基本存储单元。原子操作atomic operations一系列操作全部成功或者全部失败如果一个操作失败其他操作还原。缓存命中读取数据时会先查看高速缓存中是否存在该数据有则直接读取缓存命中没有则到内存中读取。缓冲行计算机中具有存储能力的区域内存页、硬盘栈区、CPU内部—高速缓存 。在计算机中最小的物理单元是1字节一个存储单元操作系统给内存和硬盘划分的逻辑存储是4kB一个存储单元高速缓存中的存储单元是缓冲行64字节每一个缓冲行和外界相连的导线进行数据交互。每个缓冲行都有唯一的线路一个缓冲行可以存储多个任务的数据当一个任务对其进行操作时不同芯片有所不同可能加载全部数据也可能只加载一部分数据占据该缓冲行的线路相当于占据整个缓冲行其他任务不能进行操作。缓冲行填充操作系统指挥驱动驱动调动硬件为了指挥驱动操作系统内核也是C语言。计算机底层为C语言各种复杂的类型都会转成C语言的基本类型short、int、long、float、double、char所以缓存行中存储的都是1字节到8字节的数据若存储的数据多读取数据时就需要排队找到想读取得数据影响性能。若数据量较少可以将缓存行全部用一个任务的数据填充会大幅提升性能但是数据量较多时高速缓存中存储的数据过少就会影响性能。因此设计一个缓存行的大小为64字节存储的数据量不会过大数据拥堵排队也不会等待过长时间。非学术术语写命中CPU完成操作将数据返回到高速缓存中时若数据存在则写命中若返回前高速缓存已满数据更新回内存则没有写命中。写缺失数据返回到内存中时若数据被其他线程删除无法找到该数据则写缺失2.2.2 volatile2.2.2.1 volatile解释Java代码如下instance new Singleton(); // instance是volatile变量转变成汇编代码如下。0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp)Lock前缀的指令在多核处理器下会引发了两件事情。将当前处理器缓存行的数据写回到系统内存。这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。如a20返回更新后总线会记录其地址让CPU缓存中记录该地址的a10会失效但是对b无影响。2.2.2.2 volatile一定能保证准确性吗如a20返回到达返回队列中后时间片到期a10被选中返回更新此时处于返回队列中的a20并不会失效最终会造成数据覆盖因此volatile不能保证写的准确性。所谓的返回队列表实质上是顺序指令读到某个指令时对某块数据进行更新。即使数据无效页并不会影响到顺序指令它会继续执行返回无效数据。2.2.2.3volatile的两条实现原则1Lock前缀指令会引起处理器缓存回写到内存。2一个处理器的缓存回写到内存会导致其他处理器的缓存无效。volatile返回数据时会锁住缓存中其他地址相同的数据不让其进入顺序指令但是该步骤不是原子操作若刚进入顺序指令恰好时间片到期其他地址相同的数据执行进入顺序指令则无法阻止注一般是锁缓存如果锁住总线计算机所有任务都会停止执行代价太大。2.2.2.4 代码展示public class Test { public volatile int flag 0; }public class Test1 { public static void main(String[] args) throws Exception { Test x1 new Test(); Thread t1 new Thread() { public void run() { for(int i 0;i100000;i) { x1.flag; } } }; Thread t2 new Thread() { public void run() { for(int i 0;i100000;i) { x1.flag; } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(x1.flag); } }注意 跨线程操作时会认为变量前加了final,因此需要通过引用类型的对象来对flag进行操作不改变x1的指向。即flag1 final flag1final关键字final修饰的基本类型数据不能修改值final修饰的引用类型不能改变指向final修饰的方法不能被重写final修饰的类不能被继承final有一定的防止指令重排序的功能final通常和static一起用运行结果volatile只保证读到的是最新数据但是修改时可能背的线程会对该数据进行修改造成数据覆盖。2.2.2.5 保证volatile准确数据记录其读取时候的原值返回更新时若记录的原值与内存中对应的值不一致则认为该操作失效即实现了写后读思想保证了volatile的准确性。volatile使用优化追加字节追加数据使某一个任务所使用的数据占据一个缓存行64字节此时没有其他任务来读取不需要排队读取提高性能。注意并不是所有CPU的架构都支持该方式在两种情况下不应使用该方法1.缓存行非64字节宽的处理器。如P6系列和奔腾处理器它们的L1和L2高速缓存行是32个 字节宽。2.共享变量不会被频繁地写因为使用追加字节的方式需要处理器读取更多的字节到高速 缓冲区这本身就会带来一定的性能消耗如果共享变量不被频繁写的话锁的几率也非常 小就没必要通过追加字节的方式来避免相互锁定2.3 synchronized重量级锁synchronized不能直接修饰方法外的变量能修饰方法及方法内的变量volatile能修是属性但是不能修饰方法。要实现读的准确性要实现写后读写后读一个线程写对内存的更新完之后其他线程才可以读读取内存数据。2.3.1 synchronized代码讲解synchronized实现了写后读思想public class Test { public volatile int flag 0; public synchronized void add() { flag; } }public class Test1 { public static void main(String[] args) throws Exception { Test x1 new Test(); Thread t1 new Thread() { public void run() { for(int i 0;i100000;i) { x1.add(); } } }; Thread t2 new Thread() { public void run() { for(int i 0;i100000;i) { x1.add(); } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(x1.flag); } }运行结果若线程A对资源加synchronized锁成功即使线程A时间片到期线程B也无法读取synchronized锁住的资源所以保证了写后读最终结果准确。——synchronized禁止资源同时被两个线程拷贝。2.3.2 那么加synchronized锁后结果一定准确吗public class Test1 { public static void main(String[] args) throws Exception { Test x1 new Test(); Thread t1 new Thread() { public void run() { for(int i 0;i100000;i) { int a x1.get(); x1.set(a1); } } }; Thread t2 new Thread() { public void run() { for(int i 0;i100000;i) { int a x1.get(); x1.set(a1); } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(x1.flag); } }运行结果运行结果出错的原因读和写操作分开数据读取完成后释放锁若此时时间片恰好到期其他线程可以进行读取违背了读后写。所以一定要写数据更新完成后再释放锁否则加synchronized锁无效仍会出错。多线程并发、多进程并发、多服务器、分布式集群对某个变量进行操作只要实现了写后读那么结果一定是准确的同时写后读思想无视语言不管是C、C、Java等只要实现了写后读那么结果一定是准确的注意add()、get()、set()都属于加锁的非静态方法若其中一个线程调用 get()会锁住整个对象其他线程不允许调用add()、get()或set()其他想调用该对象内方法的线程直接进入阻塞队列。public class Test { public volatile int flag 0; public synchronized void add() { flag; } public synchronized int get() { System.out.println(get开始); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(get结束); return flag; } public synchronized void set(int a) { System.out.println(set开始); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(set结束); flag a; } }public class Test1 { public static void main(String[] args) throws Exception { Test x1 new Test(); Thread t1 new Thread() { public void run() { x1.get(); } }; Thread t2 new Thread() { public void run() { x1.set(20); } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(x1.flag); } }2.3.3 synchronized锁的形式对于普通同步方法锁是当前实例对象。对于静态同步方法锁是当前类的Class对象。对于同步方法块锁是Synchonized括号里配置的对象。public synchronized void add() { String flag1 null; synchronized(flag1){ } }如上述代码锁住的是flag1。注Synchonized只能锁引用类型数据。public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); synchronized(x1){ } }注意此时Synchonized锁住的是对象对象中的加锁及不加锁的方法都不能在代码执行完毕前被访问。public class Shop { public synchronized void m1() { //对象锁 System.out.println(m1开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m1结束); } public synchronized void m2() { //对象锁 System.out.println(m2开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m2结束); } public static synchronized void m3() { //类锁 System.out.println(m3开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m3结束); } public synchronized static void m4() { //类锁 static和synchronized都是修饰词先后顺序没有要求 System.out.println(m4开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m4结束); } public void m5() { System.out.println(m5开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m5结束); } public static void m6() { System.out.println(m6开始); try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(m6结束); } }public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { } }; Thread t2 new Thread() { public void run() { } }; t1.start(); t2.start(); } }内存图2.3.3.1 对象锁public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m1(); } }; Thread t2 new Thread() { public void run() { x1.m1(); } }; t1.start(); t2.start(); } }两个线程中同一个对象的m1()不能同时运行。public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m1(); } }; Thread t2 new Thread() { public void run() { x1.m2(); } }; t1.start(); t2.start(); } }两个线程中同一个对象的m1()和m2()不能同时运行。public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m1(); } }; Thread t2 new Thread() { public void run() { x2.m1(); } }; t1.start(); t2.start(); } }两个线程中不同对象的m1()能同时运行。public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m1(); } }; Thread t2 new Thread() { public void run() { x1.m3(); } }; t1.start(); t2.start(); } }m3()方法位于静态常量池锁住对象对其没有影响。对象锁一个线程调用方法时会锁住整个对象不允许另外一个线程想调用该对象中的其他方法但是不同对象中的同一方法可以调用。》非静态方法在每个对象中都有一份。2.3.3.2 类锁public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m4(); } }; Thread t2 new Thread() { public void run() { x1.m3(); } }; t1.start(); t2.start(); } }m3()和m4()是静态方法会锁住类常量池中的类不能同时进行调用。public class Test1 { public static void main(String[] aaa) throws Exception { Shop x1 new Shop(); Shop x2 new Shop(); Thread t1 new Thread() { public void run() { x1.m1(); } }; Thread t2 new Thread() { public void run() { x1.m5(); } }; t1.start(); t2.start(); } }对象锁及类锁的规则仅针对加锁的方法不加锁的方法如m5()不受影响 同一类锁只影响同一类锁对象锁和类锁不会互相影响不是同一个对象互不影响。

相关新闻

Remotion 图片处理完全指南:从 `<Img>` 布局定位到 `getImageDimensions` 动态取图

Remotion 图片处理完全指南:从 `<Img>` 布局定位到 `getImageDimensions` 动态取图

2026/9/8 19:23:24

Remotion 图片处理完全指南&#xff1a;从 <Img> 布局定位到 getImageDimensions 动态取图 【免费下载链接】remotion &#x1f3a5; Make videos programmatically with React 项目地址: https://gitcode.com/GitHub_Trending/re/remotion 本文围绕 Remotion 技能…

Clawdbot深度拆解:AI代理、爬虫机器人还是自动化智能体?

Clawdbot深度拆解:AI代理、爬虫机器人还是自动化智能体?

2026/9/8 19:23:24

最近后台好几个朋友留言&#xff0c;都在问同一个词&#xff1a;Clawdbot。说实话&#xff0c;我第一次拿到这个词的时候也愣了一下&#xff0c;因为市面上叫“XX bot”的产品太多了&#xff0c;有些是真有落地案例&#xff0c;有些还停在概念阶段。但把它当成一个商业和技术命…

Impeccable colorize 指南:在单色界面上建立有层次的战略性配色系统

Impeccable colorize 指南:在单色界面上建立有层次的战略性配色系统

2026/9/8 19:23:24

Impeccable colorize 指南&#xff1a;在单色界面上建立有层次的战略性配色系统 【免费下载链接】impeccable The design language that makes your AI harness better at design. 项目地址: https://gitcode.com/GitHub_Trending/im/impeccable 本文面向使用 Impeccabl…

LangChain核心三件事:链式抽象、Agent组件与LangGraph编排

LangChain核心三件事:链式抽象、Agent组件与LangGraph编排

2026/9/8 20:13:26

这两年做 AI 应用开发&#xff0c;如果你还没碰过 LangChain 生态&#xff0c;那确实有点说不过去了。从最简单的“把 Prompt 接到大模型上”到复杂的多步骤工作流、智能体系统&#xff0c;LangChain 几乎把开发过程中的方方面面都抽象成了可组合的模块。但问题也出在这&#x…

论文盲审意见怎么应对?修改策略一篇讲透

论文盲审意见怎么应对?修改策略一篇讲透

2026/9/8 20:13:26

拿到盲审意见的那一刻&#xff0c;很多人会先紧张&#xff1a;措辞犀利&#xff0c;结论栏又不明朗&#xff0c;完全不知道怎么应对。这篇把盲审返修拆成「读懂意见—定位修改点—逐条落实—复查回稿」四个阶段&#xff0c;每个阶段给出可执行的修改策略&#xff0c;帮你把「不…

微信小程序体验版白屏?用PageSpy远程调试实战指南

微信小程序体验版白屏?用PageSpy远程调试实战指南

2026/9/8 20:13:26

“体验版打开白屏&#xff0c;开发版一切正常”——这是我做微信小程序这两年被问得最多的一个问题。早些年遇到这种情况&#xff0c;要么让用户反复切网络、清缓存&#xff0c;要么把console.log加上一堆然后远程指挥用户点开调试面板&#xff0c;效率低得离谱。后来接触到Pag…

20分钟用ADB给安卓去预装:UAD 精简实操

20分钟用ADB给安卓去预装:UAD 精简实操

2026/9/8 20:13:26

20分钟用ADB给安卓去预装&#xff1a;UAD 精简实操 【免费下载链接】universal-android-debloater Cross-platform GUI written in Rust using ADB to debloat non-rooted android devices. Improve your privacy, the security and battery life of your device. 项目地址: …

论文文献综述被说堆砌?归纳梳理的4步清单

论文文献综述被说堆砌?归纳梳理的4步清单

2026/9/8 20:13:26

文献综述写了一大段&#xff0c;导师却批了一句「堆砌罗列、只有叙述没有观点」——不少同学交初稿时都怕撞上这句反馈。问题往往不在读得少&#xff0c;而在写时只做了搬运、没做归纳。这篇把「罗列式综述如何改写成有主线的综述」拆成 4 步可照做的工序&#xff0c;文末附段落…

电力监控设备电源选型指南:输入范围、隔离耐压与纹波噪声实战解析

电力监控设备电源选型指南:输入范围、隔离耐压与纹波噪声实战解析

2026/9/8 20:03:25

做电力监控的设备&#xff0c;电源选型从来不是最后随便挑一个模块焊上去的事。前阵子有个同行跟我聊&#xff0c;他们在做配电房综合监控终端&#xff0c;样机阶段一切正常&#xff0c;一到现场就频繁死机重启&#xff0c;查来查去最后发现是电源模块输入电压范围选窄了&#…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/7 20:21:46

本文首发于“生态学者”&#xff01;从“湿地面积”到“土壤碳密度”&#xff1a;为什么需要重新认识潮汐湿地蓝碳变化&#xff1f;潮汐湿地位于陆地与海洋的交汇地带&#xff0c;包括红树林、盐沼和潮滩&#xff0c;是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/8 4:55:53

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包&#xff0c;并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身&#xff1a;从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中&#xff0c;容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务&#xff0c;镜像体积通常被严格控制在 50MB 到 200MB 以内&#xff0c;拉取镜像只…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景&#xff1a;客户拿着一条良率曲线截图问你&#xff0c;这批货的良率怎么掉了三个点&#xff0c;是不是工艺出问题了&#xff0c;产生的不良会不会流到他们产线上去。你解释了半天&#xff0c;客户似懂非懂&#…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle&#xff0c;这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了&#xff0c;而且很有意思的是&#xff0c;它经常不是新手专属——很多写了好几年模型的老手&#xff0c;在从单机改成自定义采样器&#xf…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频&#xff0c;声称中国电动车不仅性能不如美国大排量车型&#xff0c;安全性也堪忧。然而事实恰恰相反&#xff0c;GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴&#xff08;Euro NCAP&#xff09;测试中全部斩获满分。就在特斯…

远程协作的工作台整理

远程协作的工作台整理

2026/9/8 4:23:39

远程协作的工作台整理远程协作的核心不是再加一个工具&#xff0c;而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置&#xff1b;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/8 3:19:39

持续集成 流水线自动化与 声明式交付 实践&#xff1a;原型怎样变成可用功能分类&#xff1a;[AI/大模型]细分主题&#xff1a;AI 增强型 CI/CD 流水线自动化与 GitOps 实践&#xff1a;Agent 工作流、工具调用与任务拆解&#xff1a;从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/8 4:00:23

容器编排 生产环境运维与排障实战&#xff1a;复盘记录怎样真正派上用场分类&#xff1a;[工程技术]细分主题&#xff1a;Kubernetes 生产环境运维与排障实战&#xff1a;可复制的项目复盘模板与决策记录大部分团队的事故复盘报告&#xff0c;最后都变成了躺在 Confluence 或钉…