在这个小结里面重点讨论原子操作的原理和设计思想。
由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念。
在Java Concurrency in Practice中是这样定义线程安全的:
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。
显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的。
原子操作的描述是: 多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的。
枯燥的定义介绍完了,下面说更枯燥的理论知识。
指令重排序
Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。
程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫做顺序化一致性模型。但是现代计算机体系和处理器架构都不保证这一点(因为人为的指定并不能总是保证符合CPU处理的特性)。
我们来看最经典的一个案例。
package xylz.study.concurrency.atomic;
public class ReorderingDemo {
static int x = 0, y = 0, a = 0, b = 0;
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
x=y=a=b=0;
Thread one = new Thread() {
public void run() {
a = 1;
x = b;
}
};
Thread two = new Thread() {
public void run() {
b = 1;
y = a;
}
};
one.start();
two.start();
one.join();
two.join();
System.out.println(x + " " + y);
}
}
}
在这个例子中one/two两个线程修改区x,y,a,b四个变量,在执行100次的情况下,可能得到(0 1)或者(1 0)或者(1 1)。事实上按照JVM的规范以及CPU的特性有很可能得到(0 0)。当然上面的代码大家不一定能得到(0 0),因为run()里面的操作过于简单,可能比启动一个线程花费的时间还少,因此上面的例子难以出现(0,0)。但是在现代CPU和JVM上确实是存在的。由于run()里面的动作对于结果是无关的,因此里面的指令可能发生指令重排序,即使是按照程序的顺序执行,数据变化刷新到主存也是需要时间的。假定是按照a=1;x=b;b=1;y=a;执行的,x=0是比较正常的,虽然a=1在y=a之前执行的,但是由于线程one执行a=1完成后还没有来得及将数据1写回主存(这时候数据是在线程one的堆栈里面的),线程two从主存中拿到的数据a可能仍然是0(显然是一个过期数据,但是是有可能的),这样就发生了数据错误。
在两个线程交替执行的情况下数据的结果就不确定了,在机器压力大,多核CPU并发执行的情况下,数据的结果就更加不确定了。
Happens-before法则
Java存储模型有一个happens-before原则,就是如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。
在介绍happens-before法则之前介绍一个概念:JMM动作(Java Memeory Model Action),Java存储模型动作。一个动作(Action)包括:变量的读写、监视器加锁和释放锁、线程的start()和join()。后面还会提到锁的的。
happens-before完整规则:
(1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。
(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。
(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
(4)Thread.start()的调用会happens-before于启动线程里面的动作。
(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
(6)一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
(7)一个对象构造函数的结束happens-before与该对象的finalizer的开始
(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。
volatile语义
到目前为止,我们多次提到volatile,但是却仍然没有理解volatile的语义。
volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。
volatile包含以下语义:
(1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。
尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!
volatile通常在下面的场景:
volatile boolean done = false;
…
while( ! done ){
dosomething();
}
应用volatile变量的三个原则:
(1)写入变量不依赖此变量的值,或者只有一个线程修改此变量
(2)变量的状态不需要与其它变量共同参与不变约束
(3)访问变量不需要加锁
这一节理论知识比较多,但是这是很面很多章节的基础,在后面的章节中会多次提到这些特性。
本小节中还是没有谈到原子操作的原理和思想,在下一节中将根据上面的一些知识来介绍原子操作。
参考资料:
(1)Java Concurrency in Practice
(2)正确使用 Volatile 变量
分享到:
相关推荐
### 深入浅出Java_Concurrency #### J.U.C的整体认识 Java的并发编程模型在J.U.C(`java.util.concurrent`)包中得到了全面的展现,这不仅仅是Java语言本身的一大亮点,更是多线程编程领域的重要组成部分。本文...
- **volatile变量**:用于确保多线程环境下的可见性和防止指令重排序。 - **原子操作与Atomic类**:如AtomicInteger、AtomicLong等,提供了无锁的原子性操作,提高并发性能。 3. **并发设计模式** - **生产者-...
这本书深入浅出地探讨了Java平台上的多线程和并发编程,为读者提供了大量实用的指导原则和最佳实践。 并发操作是现代计算机系统中的关键部分,特别是在多核处理器和分布式计算环境中,有效地利用并发可以极大地提高...
这本书深入浅出地探讨了Java平台上的并发问题,帮助读者理解和掌握如何编写高效、可靠且可维护的多线程应用程序。以下是该书及其配套PPT中涵盖的一些关键知识点: 1. **线程基础**:首先,书中会介绍线程的基本概念...
<<java并行编程>>英文版chm格式,英文名称<Java Concurrency in Practice>,一直想买这本书,但总是缺货,找到了电子版,分享给大家。 Java Concurrency in Practice By Brian Goetz, Tim Peierls, Joshua Bloch,...
在Java语言规范中,你会找到关于线程、对象的内存模型、可见性以及happens-before关系的详细描述。这些规定确保了线程之间的正确通信和内存一致性,是理解和调试并发问题的关键。 最后,"Addison_Wesley_-_...
`Java-concurrency-master.zip`这个压缩包很可能包含了关于Java并发编程的各种资料和示例,对于学习和理解Java并发机制非常有帮助。 Java并发主要包括以下几个核心知识点: 1. **线程**:Java通过`Thread`类来创建...
Java Concurrency in Practice 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者...
Java并发编程中的`AtomicLongArray`是`java.util.concurrent.atomic`包中的一种数据结构,它提供了一种原子性地操作长整型数组的方式。这个类主要用于在多线程环境中实现高效且线程安全的数组元素更新。下面我们将...
- **书名**:《Java并发实践》(Java Concurrency in Practice) - **作者**:Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea - **出版社**:Addison Wesley Professional - **...
Java Concurrency in practice
在C++编程中,异步编程是一种常见的技术,它允许程序在等待某个耗时操作完成时继续执行其他任务,从而提高程序的响应性和效率。C++11引入了并发编程的支持,包括`std::future`、`std::promise`以及`std::async`等...
Concurrency is always a challenge for developers and writing concurrent programs can be extremely hard. There is a number of things that could potentially blow up and the complexity of systems rises ...
Using the concurrency building blocks in java.util.concurrent Performance optimization dos and don'ts Testing concurrent programs Advanced topics such as atomic variables, nonblocking algorithms, ...
在这个"concurrency-master"压缩包中,你可能找到了以下关键知识点: 1. **并发基础**:包括线程的创建和管理,如`Thread`类的使用,以及通过实现`Runnable`接口来创建线程。同时,还有对`ExecutorService`和`...
《Java并发编程实践》这本书深入浅出地探讨了Java平台上的并发问题,提供了许多实用的解决方案。这里的"java_concurrency_in_practice_source"源代码正是书中实例的实现,它涵盖了Java多线程编程中的关键概念和技术...
本书深入浅出地介绍了Java 5.0及之后版本中新增加的并发特性,并对并发编程进行了全面而详尽的讲解。自发布以来,这本书因其内容的专业性和实用性受到了广泛的赞誉。 #### 重要性与背景 随着计算机硬件的发展,多核...
含下列3本电子书和2个源码jar, Java并发编程实战.pdf Java并发编程实践(全).pdf java_concurrency_in_practice.pdf jcip-examples-src.jar jcip-annotations-src.jar 英文版是高清晰的,实战和实践都是同一帮人对...