java基础复习(七):谈谈volatile

本文探讨了Java中线程安全问题的原因,包括原子性、可见性和有序性。Java内存模型(JMM)旨在解决这些问题,提供统一的内存访问规则。volatile关键字保证了变量的可见性和有序性,但无法解决原子性问题。文章详细分析了volatile的原理,强调其在多线程编程中的限制和使用场景,并通过实例解释了volatile在i++操作中的局限性,指出在需要原子性保证的情况下应使用原子类。
摘要由CSDN通过智能技术生成

线程安全问题出现的原因

程序最开始是静态的、原子的、顺序执行的,虽然CPU利用率不高(CPU总是能有时间偷懒),但是程序员需要操心的问题不多。为了提升效率,陆续引入进程和线程的概念,并且实现了相应的数据结构。程序变成了动态的、并发的、异步的、执行也不再是原子的了。CPU利用率上去了,程序员要操心的事情多了…
线程安全问题的根源可以被归纳为三点:原子性问题、可见性问题和有序性问题。

原子性

由于存在线程和进程切换,我们无法做到一气呵成的执行某段程序,因为总是存在无法避免的中断产生。这倒没什么,因为如果它们的数据如果是私有的,这种切换不会造成什么问题,肯定不会存在“一会儿聊QQ一会儿聊微信,然后QQ发出的消息跑到微信上了”,真正导致线程安全问题的是:多个线程/进程操作的不是它们私有的数据,而是共享数据!!!如果一个线程修改共享数据不是原子的,那么其他线程就会读到“改到一半”的数据——不是原子性,就意味着访问共享变量的操作不能被称之为一个事务,可能会产生读写冲突(脏读)、写写冲突(修改丢失)等

可见性

CPU访问内存需要的时钟周期,和CPU执行一条指令的时钟周期相比实在是太慢了,引入高速缓存就是为了弥补这种速度差,要求CPU减少直接访存的次数。
这虽然提升了CPU读取数据的效率,但是引入了可见性问题,每个CPU都有自己的缓存(L1/L2 ),因为多核处理器时代中,每个核心具有独立的缓存,这导致核心之间缓存同一共享变量的值可能是不一致的。

有序性

编译器和处理器都会对不同层次的指令进行重排序,来加速指令执行效率(如一次性执行所有的读操作后,再统一执行一次写操作),重排序优化的原则只能保证不影响单线程的执行结果,但是可能使多线程程序执行结果出现问题。

Java内存模型

因为不同的平台具有不同的CPU、缓存、操作系统等,JVM为了实现跨平台性,制定了统一规范,这其中就包含内存模型JMM。

JVM本质上是一组规范,是一堆接口,而不同的平台去实现这组规范,最终达成不同平台但功能一致的特点

JMM定义了线程如何访问共享变量的规则:
所有的共享变量存储在主内存中,而每个线程都具有独立的工作内存,各线程只能自己的工作内存操作变量,而且线程之间不能直接访问对方的工作内存,工作内存存放的主内存的副本,线程之间如果需要传递变量值,必须依靠主内存。
(不是操作共享变量,那就不存在线程安全问题了,没有讨论必要)

JMM就是对CPU-高速缓存-主存关系的抽象。JMM不管你的CPU和内存条是AMD还是Intel,它提供一个统一的视图,你用java写程序,就不要考虑操作系统和CPU那一套东西了,就看我给你提供内存模型JMM就可以了。

总结:JMM屏蔽了底层内存模型的差异与细节,使得java程序在各个不同底层软件和硬件平台上,都可以达到一致的内存访问效果。

JMM中的原子/可见/有序问题

既然JMM让我们“眼里只有它”,那么我们就来分析一下包上一层外衣之后,java程序存在哪些线程安全问题,依然是之前的三条:
【1】线程切换,访问共享变量的一组操作无法被看

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值