要研究原子操作,就必须要对原子操作的来龙去脉有个清晰的认识。我们从原子操作的概念,以及处理器的原子操作和Java中原子操作的实现说起。
一.原子操作的概念
我们在物理学中知道,原子是一个不可再分的最小粒子。同理,原子操作(atomic operation)就是指不可被中断的一个或者一系列操作。
先来了解几个基本概念。
二.处理器中如何实现原子操作
在<Java多线程高并发进阶篇(一)-volatile实现原理剖析>中,我们说到了总线锁和缓存锁的概念。没错,在多处理器中,就是使用总线锁和缓存锁来实现处理器之间的原子操作。
首先,我们要明确一点,处理器会自动保证基本的内存操作是原子的(要不然说处理器的内存操作有什么意义),也就是说当一个处理器访问内存中读取一个字节时,其他处理器是不能访问该字节的内存地址的(这是最基本的操作,要不然就乱套了)。
在最新型号的处理器中,都能自动保证处理器对同一个缓存行里的操作是原子的。
这些最基本操作的原子性,是处理器必须要实现的基本功能!
那么如何保证在复杂条件下的内存操作原子性(比如跨总线,跨多个缓存行,跨页表等)?
答案就是总线锁和缓存锁。我们再叙述一下这两东西。
1.总线锁
上一篇帖子中已经介绍过,我们复习一遍。
所谓总线锁,就是一个处理器对共享变量进行操作时,会在总线上声言一个LOCK#信号,当其他处理器对共享变量进行操作请求时,就会被阻塞,而该处理器就可以独占共享内存(任我行!)。
举个例子i=1,我们计算两次i++。我们预想的结果是3,但是,如果没有总线锁,是不是还能得到3?不一定,可能得到是2。
原因是多个处理器(CPU1,CPU2)同时读取自己缓存行中的i(缓存的都是1),去做操作i++,最后计算完成后都写回到主内存,那么你此时看到的就是2了。
所以,使用总线锁可以保证处理器之间的操作时原子性。
2.缓存锁(两个关键词:一个缓存行,缓存一致性协议)
我们在上一帖中也说到过,缓存锁是使用缓存一致性协议来保证处理器之间对缓存行的内存操作的原子性的。
普及一个基础知识,在内存中,频繁使用的数据会被缓存到处理器的一级,二级等高速缓存中。
所谓缓存锁定,就是在一个处理器中,对共享变量的操作不是在主内存中进行操作,而是在处理器自己的缓存中操作。当处理器要对该缓存行进行操作时,就把该缓存行对应的内存地址进行锁定。其他处理器要操作时,会使用嗅探技术进行探测,如果发现该地址被锁定了,那么它就会把自身存着该数据的缓存行设置为无效状态。这就保证了缓存一致性(数据始终保持一致),也就是缓存一致性协议能够阻止缓存了同一缓存行的处理器同时修改。
那什么情况下不能使用缓存锁?
一是当操作的数据根本就不能被缓存在处理器内部时候(废话),或者就是要操作的数据跨了多个缓存行(这当然,缓存锁只针对一个缓存行的数据),这时只能使用总线锁。
二是处理器不支持缓存锁。比如,Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。
Intel处理器中,多个lock前缀指令就使用了上述两种机制实现。例如,位测试和修改令:BTS、BTR、BTC;交换指令XADD、CMPXCHG,以及其他一些操作数和逻辑指令(如ADD、OR)等
三.Java中如何实现原子操作?
在Java中,实现原子操作的方法大致有两种。一种是锁机制实现,另外一个就是使用无锁实现--CAS。
1.我们重点说一下无锁实现--CAS.
无锁机制,充分利用了处理器对于原子性的保证机制。CAS在处理器内部使用CMPXCHG完成原子性操作控制。在并发包下的原子实现类中,我们举一例。
在JDK中,Unsafe类的compareAndSwapInt方法的修饰符是native和final。也就是说,我们需要深入底层,看下这个本地方法的源码实现。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp,最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp:
// Adding a lock prefix to an instruction on MP machine(添加一条lock前缀指令在MP机器上) // VC++ doesn't like the lock prefix to be on a single line // so we can't insert a label after the lock prefix. // By emitting a lock prefix, we can define a label after it. #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ __asm je L0 \ __asm _emit 0xF0 \ __asm L0: inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
看不懂c语言代码没关系,我们看它的注释,大概能明白:
根据处理器的类型(是单处理器还是多处理器,MP是指Multiple,多个的意思),来决定是否加lock前缀指令.如果是单处理器,LOCK_IF_MP不成立,就不用在cmpxchg指令前加lock前缀指令(注释说VC++不喜欢在单行上加lock前缀指令,实际上单处理器可以自己维护访问一致性,还要lock指令干啥?).如果是多处理器,那么就在在cmpxchg指令前加lock前缀指令.
2.CAS中存在的问题
①著名的ABA问题
CAS在操作变量的时候。需要检测变量的值是否发生了变化.如果没有发现变化。它就会以为没有发生过更新操作。但是当我们把值从A-->B,然后B-->A时,其实是发生过变化的。那么如何解决这个问题?
很简单,给每次操作都加一个标志戳(stamp),那么上面的变化过程不是就成了1A->2B-->3A了。
在JDK1.5后,JDK的Atomic包里提供了一个类AtomicStampedReference,来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
②循环时间长
因为我们知道,在原子包中,实现CAS操作都使用了无限循环来进行自旋操作。那么肯定会造成CPU的开销大。
③只能保证一个共享变量的原子性操作。从上图我们也可以看到,基本的原子类只能实现对单个共享变量的修改,增加等过程。那么如何解决这个问题呢?从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
相关推荐
1. **多线程**:Java提供了强大的多线程支持,这可能是第25章的重点。学生可能需要理解和掌握`Thread`类、`Runnable`接口、线程同步(如`synchronized`关键字,`wait()`, `notify()`, `notifyAll()`方法)、线程池...
Java-JUC-多线程进阶resources是 Java 并发编程的高级课程,涵盖了 Java 中的并发编程概念、线程安全、锁机制、集合类、线程池、函数式接口、Stream流式计算等多个方面。 什么是JUC JUC(Java Utilities for ...
- **原子操作与锁**:探讨Atomic类族、显式锁等技术在多线程环境下的作用。 #### 3. 面向对象设计原则与模式 - **设计模式**:深入理解单例模式、工厂模式、策略模式等常见设计模式,并探讨它们的应用场景。 - **...
本书可能会详细介绍Java中的并发机制,如线程(Thread)、同步(synchronization)、锁(lock)、原子变量(atomic variables)等。同时,也会讲解如何使用高级并发工具类如ExecutorService、Semaphore、CountDownLatch等来...
- **并发集合**:介绍Java并发包中专为多线程环境设计的集合类,如`ConcurrentHashMap`等,并给出具体使用示例。 #### 4. 并发编程 - **线程生命周期**:全面了解线程的状态变迁过程,包括创建、就绪、运行、阻塞、...
《深入学习:Java多线程编程》是一本...通过学习《深入学习:Java多线程编程》,开发者可以提升在复杂并发环境下的编程能力,为开发高并发、高可用的应用打下坚实的基础。这本书无疑是Java开发者进阶必备的参考书之一。
1. **Java基础**: 作为后端工程师,扎实的Java基础知识是必不可少的,包括但不限于语法、面向对象编程、异常处理、集合框架(如ArrayList、LinkedList、HashMap等)、多线程、I/O流、反射、序列化等。 2. **设计...
还会讲解Java提供的并发工具,如Semaphore、CyclicBarrier和CountDownLatch,以及Future和CompletableFuture,这些工具可以用来协调多线程间的操作,实现更复杂的同步策略。 在项目实战部分,学员将有机会应用所学...
以下是对Java多线程的详细解释: 1. **线程的概念**:线程是操作系统调度的基本单位,一个进程中可以包含多个线程,它们共享同一内存空间,拥有独立的程序计数器、寄存器和栈。通过多线程,程序可以同时执行多个...
8. **并行与并发**:JVM支持多线程并发,理解锁、同步、原子操作、线程池等概念,是构建高效并发程序的关键。 9. **字符串常量池**:理解字符串在JVM中的存储方式,以及String对象的intern()方法,有助于优化字符串...
这个规范对于理解Java并发编程至关重要,因为它解决了多线程环境下的可见性、原子性和一致性问题,从而提升了Java程序在多处理器系统上的性能和正确性。 **Java内存模型** Java内存模型定义了线程如何访问和修改...
这本书详细介绍了Java并发模型,包括线程、锁、同步、原子变量、并发集合、并发工具类等,并提供了大量实用的并发编程实践指导。随着多核处理器的普及,理解和掌握并发编程已经成为每个Java开发者必须面对的挑战,这...
10. **并发编程**:Java并发编程包括线程池的使用、原子变量(`Atomic*`类)、并发容器(如`ConcurrentHashMap`)和并发工具类(如`Semaphore`, `ForkJoinPool`),这些都是编写高并发应用的基础。 以上仅是部分...
总而言之,Java高级语言编程进阶版是一本针对具有一定编程基础的Java爱好者的书籍,它覆盖了从泛型到多线程,再到国际化和MVC架构的多个高级主题,目标是帮助程序员提升他们的Java编程技能到一个新的层次。
通过以上内容的学习,我们可以看到Java开发进阶不仅仅是对语言本身的理解,更重要的是对其实现原理和技术细节的深入探究。无论是多线程还是JVM,都涉及到大量复杂的概念和技术点。掌握这些知识不仅能够帮助我们写出...
通过阅读这些章节,我们可以深入了解Java平台上的多线程和并发处理机制,这对于任何需要处理高性能、高并发系统开发的Java程序员来说都至关重要。 首先,第一章通常会介绍并发编程的基础概念,包括线程的基本操作,...
- **并发集合**:`ConcurrentHashMap`、`CopyOnWriteArrayList`等集合类在高并发场景下提供更好的性能和安全性。 - **阻塞队列**:`ArrayBlockingQueue`、`LinkedBlockingQueue`等阻塞队列为多线程之间共享数据提供...
### Java多线程知识点 ...以上就是关于Java多线程的基础知识以及一些进阶概念的介绍。学习并掌握这些内容对于理解和编写高效的并发程序非常重要。希望这些知识点能够帮助你在实际开发中更好地利用多线程技术。