网易首页 > 网易号 > 正文 申请入驻

浅谈synchronized 和 volatitle 实现线程安全的策略

0
分享至

  什么是线程不安全

  对线程安全的理解就是多个线程同时操作一个共享变量时会产生意料之外的情况,这种情况就是线程不安全。注意:只有写操作才可能出现线程不安全,对共享变量只进行读操作线程是绝对安全的。

  具体线程不安全的例子有一个很经典的就是两个线程都对一个共享变量 x=0 执行 100 次自增操作,但是 x 的结果并非 200

  因此线程不安全的条件是:多线程 + 共享变量 + 写操作

  Java的内存模型

  你可能会好奇线程是如何获取共享变量的?

  Java 线程之间的通信由 Java 内存模型(简称 JMM)控制,从抽象的角度来说,JMM 定义了线程和主内存之间的抽象关系。JMM 的抽象示意图如图所示:

  从图中可以看到:

  共享变量存在于主内存中,也就是堆内存

  每一个线程都保存了一份该线程使用到的共享变量的副本

  线程读取共享变量优先从本地内存(也就是栈内存)中读取,写共享变量先写到栈内存,再写入堆内存

  线程之间对共享变量的通信只能通过堆内存

  以上只是 Java 内存模型的抽象图,实际上线程的工作模型是这样的,栈内存即是两个缓冲区

  接下来看一个线程不安全的例子:

  假设线程 A、B 操作同一个共享变量 X,初始两级 Cache 都为空

  线程A想要读取X的值,由于两级 Cache 都没有命中,因此加载堆内存中的 X=0,并缓存到两个 Cache 中

  线程 A 修改 X 的值为 1,为两个 Cache 刷新 X,再刷新到堆内存

  线程 B 想要获取 X 的值,一级缓存没有获取到,二级缓存命中,读取到 X=1

  线程B想要修改X的值为 2,先刷新自,己的一级缓存为 2,再刷新二级缓存和堆内存中的 X 为 2。目前为止一切正常,接下来重点来了

  线程 A 想要读取 X 的值,一级缓存命中此时 X=1,但是堆内存中的 X=2。可以看到线程B写入的共享变量对 X 不可见,出现了线程不安全的情况。

  由于 Java 内存机制就是这样设计的,因此多个线程操作同一个变量会产生不安全的问题,volatitle 关键字这是被设计出来解决这一问题的,它只能用于单个变量。

  volatile解决共享变量线程不安全的策略

  还是接着上面这个例子,这样定义 X

  volatile 的内存语义是:

  当一个线程对 volatile 修饰的变量进行写操作时,JMM 会立即将该线程对应的栈内存中的副本的值刷新到堆内存中;当一个线程对 volatile 修饰的变量进行读时,JMM 会清空此变量的一二级缓存,直接从堆内存中读取共享变量的值。

  volatile 可以当作一个轻量级的锁来使用,但 volatile 仅仅只能保证共享变量内存的可见性,不能保证操作共享变量的原子性,而锁(如 synchronized)能保证整段锁范围内的代码具有原子性。

  synchronized 与锁

  首先要明确的是 synchronized 不是锁,锁都是基于对象的( Object 的子类),Java 中的每一个对象都可以作为一个锁。

  synchronized 是 Java 的一个关键字,保证临界区内的代码同一时刻只能有一个线程执行。

  线程的执行代码在进入 synchronized 代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。

  sysnchronized的内部锁可以是:

  当前类的 class 字节码对象:this.getClass

  当前类的一个实例:this

  一个 Object 对象

  wait 和 notify 方法只能用于 synchronized 同步代码块内

  synchronized 的内存语义

  与 volatile 不同

  进入 synchronized 块的内存语义是把在 synchronized 块内使用到的所有共享变量从栈内存中清空,这样就只能从堆内存只读取,保证了内存可见性。退出 synchronized 块的内存语义是把 synchronized 块内对共享变量的修改刷新到堆内存。

  仔细想想,这其实也是一个加锁和解锁的过程,保证共享变量修改的可见性。

  总结

  volatile 仅能保证单个共享变量内存的可见性,不能保证原子性。而 synchronized 既可保证同步块内所有共享变量的内存可见性,又能保证其操作的原子性。

  volatile 是一个轻量级的保证内存可见性的关键字,实际上并没有加锁。因此它的性能很高。

  synchronized 是一个重量级的锁,可以用在代码块、普通方法以及静态方法上。用在代码块时锁就是 synchronized(~) 内的对象,用在普通方法时锁就是this,用在静态方法时锁就是 this.getClass()

  synchronized 保证同步块内代码的原子性,因为要进行线程上下文切换,性能较低。不过优化过后性能也还可以。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
诺贝尔物理学奖得主:相信有外星人的存在

诺贝尔物理学奖得主:相信有外星人的存在

第一财经资讯
2025-11-17 19:54:10
40分+18板+10助+9帽!联盟第1!文班亚马伤停数周,DPOY有悬念了

40分+18板+10助+9帽!联盟第1!文班亚马伤停数周,DPOY有悬念了

世界体育圈
2025-11-18 16:47:16
2025-11-19 11:47:00
域之微科技
域之微科技
给大家分享最新的微科技资讯
8文章数 52258关注度
往期回顾 全部

科技要闻

一夜封神,Gemini 3让谷歌找回“碾压感”

头条要闻

女子参加模特大赛夺"广东冠军" 因突破大众审美引争议

头条要闻

女子参加模特大赛夺"广东冠军" 因突破大众审美引争议

体育要闻

结束最后一次对决,陈梦和朱雨玲笑着相拥

娱乐要闻

又反转!曝喻恩泰出轨美女律师

财经要闻

黄金税改两周,水贝低价神话终结?

汽车要闻

脱胎换骨的优秀底盘Get 新款享界S9动态驾驶体验

态度原创

旅游
艺术
亲子
公开课
军事航空

旅游要闻

嘿重庆丨一踏上这条悬空栈桥,要出片的心马上稳了

艺术要闻

启功:我是画家,但书名超过了画名

亲子要闻

科普|别再瞎补了!如何科学提升宝宝免疫力

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

量大管饱 中国军网在海外发布备战视频

无障碍浏览 进入关怀版