原子性与可见性

一、定义

1.可见性

在多核处理器中,如果多个线程对一个变量(假设)进行操作,但是这多个线程有可能被分配到多个处理器中运行,那么编译器会对代码进行优化,当线程要 处理该变量时,多个处理器会将变量从主存复制一份分别存储在自己的片上存储器中,等到进行完操作后,再赋值回主存。(这样做的好处是提高了运行的速度,因 为在处理过程中多个处理器减少了同主存通信的次数);同样在单核处理器中这样由于“备份”造成的问题同样存在!

这样的优化带来的问题之一是变量可见性——如果线程t1与线程t2分别被安排在了不同的处理器上面,那么t1与t2对于变量A的修改时相互不可见,如果t1给A赋值,然后t2又赋新值,那么t2的操作就将t1的操作覆盖掉了,这样会产生不可预料的结果。所以,即使有些操作时原子性的,但是如果不具有可见性,那么多个处理器中备份的存在就会使原子性失去意义。

2.原子性:

众所周知,原子是构成物质的基本单位(当然电子等暂且不论),所以原子的意思代表着——“不可分”;

由不可分性可知,原子性是拒绝多线程操作的(只有分解为多步操作,多个线程才能对其操作:就像一个盒子里有多个兵乓球,多个人能够从盒子里拿乒乓球;如果盒子只有一个兵乓球,一个人拿的话,其他人就拿不到了;这就是原子性,乒乓球就具有原子性,人就相当于原子)

简而言之——不被线程调度器中断的操作,如:

赋值或者return。比如"a = 1;"和 "return a;"这样的操作都具有原子性

原子性不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作!

3.非原子性操作

类似"a += b"这样的操作不具有原子性,在某些JVM中"a += b"可能要经过这样三个步骤:

(1)取出a和b

(2)计算a+b

(3)将计算结果写入内存

如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,t2执行完毕 后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。所以上面的买碘片例子在同步add方法之前,实际结果总是小于 预期结果的,因为很多操作都被无视掉了。

类似的,像"a++"这样的操作也都不具有原子性。所以在多线程的环境下一定要记得进行同步操作

4.原子性与可见性的关系

原子性与可见性并没有直接关联的关系。说道这里,不得不要讨论一下多线程带来的问题及其本质。

(1)先来点废话,有可能会将多核与单核处理器进行不同的区分,这里我搞混了,其实在代码级别来说它们是相同的!

单核机器的多线程其实是为每个线程分配一个时间片段,所以实际上这些线程在微观来说在一个时间段内只有一个在执行。这里产生的问题是如果一个线程操 作一个内存空间然后突然被线程调度器终止掉(挂起),由另一个线程获取CPU时间来对这个空间进行操作,那么着之间会产生不可预知的问题。

多核机器的基本原理与此是相同的,不同的是在同一时间,可能会有多个线程同时在进行操作(因为每个核心都可运行一项操作)。前面讲到,多核机器由于多核的原因其多个线程对于相同内存的操作会产生可见性的问题。(可见性在单核和多核中同样都存在)

(2)多线程中可见性造成的问题:

多个线程对相同变量的修改相互不可见,导致某部分操作被覆盖,比如:

count++; t1与t2两个线程准备操作它,当t1在自己存储空间内修改完count值之后,并没有及时将count修改回去,而是执行了count其它的操作——这 时候,t2开始执行该操作,但是它并没有发现count值进行了改变,这样就造成了count值没有被及时更新而产生的相关错误。

(3)其它问题:

同样是count++语句,产生问题的语句还可能是其它原因造成的:t1与t2执行该语句,t1只比t2稍慢一点,t2修改后count,t1又将自己的结果写入count,这样t1的结果会对t2的结果进行覆盖,这种覆盖会造成一项不到的错误。

(1.2)非原子性造成的问题,多个线程在执行动作时某一方的“动作”“覆盖”了另一方;

(5)讨论:

可见性的问题造成了多线程的问题的一部分,确定变量的可见性只能解决一部分多线程的问题;而操作原子性是解决多线程的总的方法,因为它拒绝多个线程在同一时刻操作相同的一段内存。

5.volatile与synchronized关键字

(1)volatile

volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而 且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。文摘:

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 就跟C中的一样 禁止编译器进行优化~~~~

注意:

如果给一个变量加上volatile修饰符,就相当于:每一个线程中一旦这个值发生了变化就马上刷新回主存,使得各个线程取出的值相同。编译器不要 对这个变量的读、写操作做优化。但是值得注意的是,除了对long和double的简单操作之外,volatile并不能提供原子性。所以,就算你将一个 变量修饰为volatile,但是对这个变量的操作并不是原子的,在并发环境下,还是不能避免错误的发生!

参考链接: http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

(2)synchronized

synchronized为一段操作或内存进行加锁,它具有互斥性。当线程要操作被synchronized修饰的内存或操作时,必须首先获得锁才能进行后续操作;但是在同一时刻只能有一个线程获得相同的一把锁(对象监视器),所以它只允许一个线程进行操作。

简单的理解方法:

synchronized(object) method();

这相当与为menthod()加了一把锁,这把锁就是object对象;当线程要访问method方法时,需要获取钥匙:object的对象监视 器,如果该钥匙没人拿走(之前没有线程操作该方法或操作完成),则当前线程拿走钥匙(获取对象监视器),并操作方法;当操作完方法后,将“钥匙”放回原 处!

如果“钥匙”不在原处,则该线程需要等待别人把钥匙放回来(等待即进入阻塞状态);如果多个线程要获取该钥匙,则它们需要进行“竞争”(一般是根据线程的优先级进行竞争)

时间: 2024-10-11 16:32:20

原子性与可见性的相关文章

深入理解Java虚拟机笔记---原子性、可见性、有序性

Java内存模型是围绕着并发过程中如何处理原子性.可见性.有序性这三个特征来建立的,下面是这三个特性的实现原理: 1.原子性(Atomicity) 由Java内存模型来直接保证的原子性变量操作包括read.load.use.assign.store和write六个,大致可以认为基础数据类型的访问和读写是具备原子性的.如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock与unlock操作直接开放给用户使用,但是却提供了更

并发编程之原子性、可见性、有序性的简单理解

并发程序正确地执行,必须要保证原子性.可见性以及有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行. 可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值. 有序性:程序执行的顺序按照代码的先后顺序执行. 对于单线程,在执行代码时jvm会进行指令重排序,处理器为了提高效率,可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保

volatile关键字、原子性和可见性

1.volatile关键字 理解volatile的关键首先要理解处理器缓存和主存. 如果将一个域声明为volatile,那么只要对这个域产生了写操作,那么所有读操作都可以看到这个修改,即volatile域的写操作会向主存刷新. 同步synchronized也会导致向主存中刷新,所以如果一个域完全由synchronized保护就不必设置为volatile. 2.原子性和可见性 可见性: 原子性:

内存管理_原子性、可见性、有序性

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行 比如存取款操作,存款和取款操作必须全部完成,或者全部不完成. 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值. eg: //Thread 1 int i = 0; i = 10; //Thread 2 j = i; 假若执行Thread1的是CPU0,执行Thread2的是CPU1.由上面的分析可知,当Thread1执行 i =10这句时,会先把i的初始

并发之原子性、可见性、有序性

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

java并发特性:原子性、可见性、有序性

要想并发程序正确地执行,必须要保证原子性.可见性以及有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并发的情况下,就不会出现变量被修改的情况 比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作.再比如:a++: 这个操作实际是a = a + 1:是可分割的,所以

7.三大性质总结:原子性、可见性以及有序性

1. 三大性质简介 在并发编程中分析线程安全的问题时往往需要切入点,那就是两大核心:JMM抽象内存模型以及happens-before规则(在这篇文章中已经经过了),三条性质:原子性,有序性和可见性.关于synchronized和volatile已经讨论过了,就想着将并发编程中这两大神器在 原子性,有序性和可见性上做一个比较,当然这也是面试中的高频考点,值得注意. 2. 原子性 原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着"同生共死"的感觉.及时在多个线程一起

Java并发编程三个性质:原子性、可见性、有序性

并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确 线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的CPU调度顺序,线程个数.指令重排的影响,偶然触发 线程安全的定义 比如说一个类,不论通过怎样的调度执行顺序,并且调用处不用对其进行同步操作,其都能表现出正确的行为,则这个类就是线程安全的 并发编程三个概念 原子性: 一个操作或多个操作要么全部执行且执行过程不被中断,要么不执行 可见性: 多个线程修改同一

线程的共享性、互斥性、原子性、可见性、有序性

参考链接:http://www.cnblogs.com/paddix/p/5374810.html 一.共享性 多个线程之间共享同一个变量,容易引发多线程安全问题.反之,如果每个数据都只是在自己的线程中使用,只属于某一个线程,那么这个数据则为安全的. 二.互斥性 资源互斥是指只允许一个访问者对其进行访问,具有唯一性和排他性.对于共享数据,如果我们只进行读数据,即使没有互斥性,我们也不需要担心其安全,但如果是写操作,则容易引起线程安全问题. 三.原子性 原子性是指对数据的操作是一个独立的.不可分割