Java中volatile和synchronized的区别

我想知道区别声明一个variables为volatile并总是访问Java中的synchronized(this)块(尤其是J2ME)中的variables?

根据这篇文章http://www.javamex.com/tutorials/synchronization_volatile.shtml有很多可以说,有很多的差异,但也有一些相似之处。

我对这条信息特别感兴趣:

  • 访问一个volatilevariables永远不会有阻塞的可能性:我们只做过一个简单的读或写操作,所以与synchronized块不同,我们永远不会锁住任何锁;
  • 因为访问一个volatilevariables永远不会拥有一个锁,所以它不适合我们想要读取更新 – 写入为primefaces操作的情况(除非我们准备“错过更新”);

他们是什么意思阅读更新写 ? 不写也是一个更新,或者他们只是意味着更新是依赖于读取信息的写入?

最重要的是,何时声明variablesvolatile比通过synchronized访问variables更合适? 对依赖于input的variables使用volatile是一个好主意吗? 例如,有一个variables叫render ,通过渲染循环读取并通过按键事件设置?

理解线程安全有两个方面是很重要的:(1)执行控制;(2)内存可见性。 第一个与代码执行时的控制(包括执行指令的顺序)以及是否可以并发执行有关,第二个是当内存中的效果对其他线程可见时执行。 由于每个CPU在其与主内存之间具有多个级别的高速caching,运行在不同CPU或内核上的线程可以在任何给定的时刻以不同的方式看到“内存”,因为允许线程获取和工作在主内存的私有副本上。

使用synchronized可以防止任何其他线程获取相同对象的监视器(或locking),从而防止同一对象上同步保护的所有代码块不会同时执行。 同步还会创build一个“发生在之前”的内存屏障,导致内存可见性约束,以至于某个线程释放锁的任何事情都会出现在另一个线程上,随后获取之前发生的锁。 实际上,在当前的硬件上,这通常会在获取监视器时导致CPUcaching刷新,并且在释放时写入主存储器,两者都(相对)昂贵。

另一方面,使用volatile强制所有对volatilevariables的访问(读或写)到主内存,从而有效地将volatilevariables保存在CPUcaching之外。 这对于一些只需要variables的可见性是正确的并且访问次序不重要的行为是有用的。 使用volatile还会改变longdouble处理,要求访问它们是primefaces的; 在一些(较旧的)硬件上,这可能需要锁,尽pipe不是现代的64位硬件。 在Java 5+的新的(JSR-133)内存模型下,volatile的语义已经得到加强,几乎和内存可见性和指令sorting同步一样强大(参见http://www.cs.umd.edu /users/pugh/java/memoryModel/jsr-133-faq.html#volatile )。 出于可视性的目的,对易失性字段的每次访问就像半个同步一样。

在新的内存模型下,volatilevariables不能彼此重新sorting。 不同之处在于,现在不再那么容易对他们周围的正常字段进行重新sorting。 写入易失性字段具有与显示器发行版相同的记忆效应,从易失性字段读取与监视器获取的记忆效应相同。 实际上,由于新的内存模型对易失性字段访问进行其他字段访问的重新sorting有更严格的限制,不pipe是否为volatile,当线程A写入易失性字段f时对于线程B来说是可见的任何东西在读取f时对线程B可见的。

– JSR 133(Java内存模型)常见问题

所以,现在两种forms的内存屏障(在当前的JMM下)都会导致指令重新sorting,从而阻止编译器或运行时间通过屏障重新sorting指令。 在旧的JMM中,volatile并不妨碍重新sorting。 这一点很重要,因为除了内存屏障之外,唯一强加的限制是, 对于任何特定的线程 ,代码的净效果与如果指令按照它们出现在资源。

volatile的一个用途是共享的,但不可变的对象是在运行中重新创build的,许多其他线程在其执行周期中的特定点处引用该对象。 一个需要其他线程开始使用重新创build的对象一旦发布,但不需要完全同步的额外开销,并伴随着争用和caching刷新。

 // Declaration public class SharedLocation { static public SomeObject someObject=new SomeObject(); // default object } // Publishing code // Note: do not simply use SharedLocation.someObject.xxx(), since although // someObject will be internally consistent for xxx(), a subsequent // call to yyy() might be inconsistent with xxx() if the object was // replaced in between calls. SharedLocation.someObject=new SomeObject(...); // new object is published // Using code private String getError() { SomeObject myCopy=SharedLocation.someObject; // gets current copy ... int cod=myCopy.getErrorCode(); String txt=myCopy.getErrorText(); return (cod+" - "+txt); } // And so on, with myCopy always in a consistent state within and across calls // Eventually we will return to the code that gets the current SomeObject. 

谈到你的阅读更新问题,具体来说。 考虑以下不安全的代码:

 public void updateCounter() { if(counter==1000) { counter=0; } else { counter++; } } 

现在,在updateCounter()方法不同步的情况下,两个线程可以同时input。 在可能发生的许多变化中,一个是thread-1对counter == 1000进行testing,发现它是真的,然后被暂停。 然后线程2做相同的testing,也看到它是真实的,并被暂停。 然后,线程1恢复并将计数器设置为0.然后线程2恢复并再次将计数器设置为0,因为它错过了线程1的更新。 这也可能发生,即使线程切换没有像我所描述的那样发生,但仅仅是因为在两个不同的CPU内核中存在两个不同的caching的计数器副本,并且每个线程都在单独的内核上运行。 对于这个问题,一个线程可能只有一个值,而另一个线程可能由于caching而具有完全不同的值。

在这个例子中,重要的是variables计数器被从主存储器读入到高速caching中,在高速caching中被更新,并且在稍后发生内存障碍或者其他事情需要高速caching时,在某个不确定点写回主存储器。 由于最大值和赋值的testing是离散操作,包括增量,这是一组非read+increment+write的primefacesread+increment+write机器指令,使得计数器volatile不足以满足该代码的线程安全性,例如:

 MOV EAX,counter INC EAX MOV counter,EAX 

只有在对它们执行的所有操作都是“primefaces”的情况下,易失性variables才是有用的,比如我的例子,其中对完全形成的对象的引用仅被读取或写入(实际上,通常它只是从单个点写入)。 另一个例子是支持写入时复制列表的易失性数组引用,前提是只能通过先读取引用的本地副本来读取数组。

volatile是一个字段修饰符同步修改代码块方法 。 所以我们可以使用这两个关键字指定一个简单访问器的三个变体:

  int i1; int geti1() {return i1;} volatile int i2; int geti2() {return i2;} int i3; synchronized int geti3() {return i3;} 

geti1()访问当前线程中当前存储在i1中的值。 线程可以有variables的本地副本,数据不必与其他线程中保存的数据相同。特别是,另一个线程可能在其线程中更新了i1 ,但是当前线程中的值可能不同于那更新的价值。 事实上,Java有一个“主”内存的概念,这是为variables保存当前“正确”值的内存。 线程可以有自己的variables副本,线程副本可以不同于“主”内存。 所以事实上,对于i1 ,“主”存储器的值可以是1,对于i1 ,thread1的值为2 ,如果thread1thread2的值都是更新的,则对于i1 来说thread2的值为3 i1但这些更新的值尚未传播到“主”内存或其他线程。

另一方面, geti2()有效地从“main”内存访问i2的值。 一个volatilevariables不允许有一个variables的本地副本,这个variables与当前保存在“main”内存中的值不同。 实际上,一个声明为volatile的variables必须使数据在所有线程中保持同步,以便每当访问或更新任何线程中的variables时,所有其他线程立即看到相同的值。 一般来说,volatilevariables比“plain”variables具有更高的访问和更新开销。 一般来说,线程可以拥有自己的数据副本,以提高效率。

volitile和synchronized有两个区别。

首先同步获取并释放监视器上的锁,一次只能强制一个线程执行代码块。 这是众所周知的同步方面。 但同步也同步内存。 实际上,同步将整个线程内存与“主”内存同步。 所以执行geti3()会执行以下操作:

  1. 线程获取监视器上对象的锁。
  2. 线程内存刷新其所有variables,即它的所有variables都能从“主”内存中有效地读取。
  3. 代码块被执行(在这种情况下,将返回值设置为i3的当前值,该值可能刚从“主”存储器复位)。
  4. (对variables的任何更改现在通常会写入“主”内存,但对于geti3(),我们没有任何更改。)
  5. 线程释放监视器上的锁对象this。

因此,volatile只在同步线程内存和“main”内存之间的一个variables的值时进行同步,同步所有线程内存和“main”内存之间的variables值,并locking和释放显示器以启动。 明显的同步可能会有更多的开销比易变。

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

synchronized是方法级别/块级访问限制修饰符。 它将确保一个线程拥有关键部分的locking。 只有拥有锁的线程才能进入synchronized块。 如果其他线程试图访问这个关键部分,他们必须等到当前所有者释放locking。

volatile是variables访问修饰符,它强制所有线程从主内存中获取variables的最新值。 访问volatilevariables不需要locking。 所有线程可以同时访问volatilevariables值。

使用volatilevariables的一个很好的例子: Datevariables。

假设你已经使Datevariablesvolatile 。 所有访问此variables的线程总是从主内存中获取最新数据,以便所有线程都显示实际的(实际)date值。 对于相同的variables,您不需要不同的线程显示不同的时间。 所有线程都应显示正确的date值。

在这里输入图像描述

看看这篇文章,以更好地理解volatile概念。

Lawrence Dol cleary解释了你read-write-update query

关于你的其他查询

什么时候更适合声明variablesvolatile,而不是通过同步访问?

如果您认为所有线程都应该实时获取variables的实际值,则必须使用volatile ,就像我为Datevariables所解释的示例一样。

对依赖于input的variables使用volatile会是一个好主意吗?

答案将与第一个查询相同。

请参阅本文以获得更好的理解。

  1. java中的volatile关键字是一个字段修饰符, synchronized修改代码块和方法。

  2. synchronized获取并释放监视器的java volatile关键字不需要。

  3. Java中的线程可以被阻塞,以便在synchronized情况下等待任何监视器,而Java中的volatile关键字则不是这种情况。

  4. 在Java中, synchronized方法比volatile关键字影响性能。

  5. 由于Java中的volatile关键字只同步线程内存和“主”内存之间的一个variables的值,而synchronized关键字同步线程内存和“主”内存之间的所有variables的值,并locking和释放监视器以启动。 由于这个原因,Java中的synchronized关键字可能会比volatile更有开销。

  6. 你不能在空对象上同步,但是你的Java中的volatilevariables可以为null。

  7. 从Java 5开始写入volatile字段与监视器版本具有相同的记忆效应,从易失性字段读取与监视器获取具有相同的记忆效应