Difference between volatile and synchronized in Java
我想知道将变量声明为EDCOX1(0),在Java中的EDOCX1×1块中总是访问变量吗?
根据本文http://www.javamex.com/tutorials/synchronization_volatile.shtml,有很多话要说,有很多不同之处,但也有一些相似之处。
我对这条信息特别感兴趣:
...
- access to a volatile variable never has the potential to block: we're only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
- because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-write as an atomic operation (unless we're prepared to"miss an update");
读更新写是什么意思?写操作不是一个更新操作,还是仅仅意味着更新操作是一个依赖于读操作的写入操作?
最重要的是,什么时候更适合声明变量
理解线程安全有两个方面很重要。好的。
第一个问题与控制代码何时执行(包括执行指令的顺序)以及它是否可以并发执行有关,第二个问题与其他线程在内存中看到所做操作的效果有关。因为每个CPU在它和主内存之间都有几个级别的缓存,所以在不同的CPU或核心上运行的线程在任何给定时刻都可以看到不同的"内存",因为线程可以获得主内存的私有副本并在其上工作。好的。
使用
另一方面,使用
在新的内存模型下,不稳定变量仍然不能相互重新排序。不同的是,现在对它们周围的正常字段访问重新排序不再那么容易了。写入易失性字段与监视器释放具有相同的内存效果,从易失性字段读取与监视器获取具有相同的内存效果。实际上,由于新的内存模型对易失性字段访问与其他字段访问(无论是否易失性)的重新排序施加了更严格的约束,因此线程
——符合JSR(Java内存模型(133)常见问题解答
步></
所以,现在这两个形式的内存屏障(根据目前的JMM)产生一个屏障,防止重新排序指令在编译或运行时的指令重新排序,从对面的屏障。在旧的jmm,防止挥发性没有重新排序。这可以是重要的,因为除了记忆障碍,唯一的限制是强加的任何特定的线程,这样,在网络效应的代码是相同的,如果它的将是在指令执行的顺序中,他们appear准确的源代码。
一个使用挥发性是共享的对象是不变的,但recreated飞,与许多其他的线程,以在该对象的参考点是在其执行周期。另一个线程需要开始使用,它是一recreated对象上,而不需要额外的开销,完全同步与它的日志和缓存的争用的冲洗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 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. |
你说读写的问题,新的,具体的。考虑下面的代码:不安全
1 2 3 4 | public void updateCounter() { if(counter==1000) { counter=0; } else { counter++; } } |
现在,updatecounter()方法的非同步双线程,它可以输入在同一时间。许多可能的子排列序列所发生的是,一个线程(1),该计数器测试= 1000和发现真实和再悬浮。多线程测试2相同的位置,也不真实,是暂停。然后线程1套和总结计数器到0。然后总结了集线程计数器2和0°,因为它想从线程1的更新。这也可以发生即使没有发生线程切换的描述,因为我有一个简单的,但不同的缓存副本的两个计数器的CPU是目前在两不同的颜色与每个单独的线程运行在线的核心。这是一个问题,线程可以在一个计数器的计数器值和其他一些可能有完全不同的价值(Just Because of缓存。
什么是重要的是,在这个例子中的变量是从主存储器读取计数器的更新到高速缓存中,高速缓存和主存储器的写回只在一些点后indeterminate时发生内存屏障内存(缓存或当别人所需要的东西。制造柜台
1 2 3 | MOV EAX,counter INC EAX MOV counter,EAX |
变量是有用的,只有当所有的物质都是做在线业务的"原子",如我的例子在参考到的完全形成的对象仅仅是一个读或写的(和,事实上,它只是typically书面从单点)。另一个例子将是挥发性阵列基准收益的在线列表复制写入阵列读,只提供对第一参考位置,以它的副本。
好的。
volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:
1
2
3
4
5
6
7
8 int i1;
int geti1() {return i1;}
volatile int i2;
int geti2() {return i2;}
int i3;
synchronized int geti3() {return i3;}
geti1() accesses the value currently stored ini1 in the current thread.
Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads.In particular, another thread may have updatedi1 in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a"main" memory, and this is the memory that holds the current"correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the"main" memory. So in fact, it is possible for the"main" memory to have a value of 1 fori1 , for thread1 to have a value of 2 fori1 and for thread2 to have a value of 3 fori1 if thread1 and thread2 have both updated i1 but those updated value has not yet been propagated to"main" memory or other threads.On the other hand,
geti2() effectively accesses the value ofi2 from"main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in"main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Generally volatile variables have a higher access and update overhead than"plain" variables. Generally threads are allowed to have their own copy of data is for better efficiency.There are two differences between volitile and synchronized.
Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block. That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with"main" memory. So executing
geti3() does the following:The thread acquires the lock on the monitor for object this . The thread memory flushes all its variables, i.e. it has all of its variables effectively read from"main" memory . The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from"main" memory). (Any changes to variables would normally now be written out to"main" memory, but for geti3() we have no changes.) The thread releases the lock on the monitor for object this. So where volatile only synchronizes the value of one variable between thread memory and"main" memory, synchronized synchronizes the value of all variables between thread memory and"main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.
http:///2007/12/difference-between-volatile-and.html javaexp.blogspot.com
一个方法
是一个很好的例子:使用挥发性
认为你有约会
看看在这篇文章
我以为你
关于你的其他查询
When is it more suitable to declare variables volatile than access them through synchronized?
你有使用
Is it a good idea to use volatile for variables that depend on input?
同样的答案将在第一个查询。
这篇文章的问题,为更好的理解。
我喜欢詹科夫的解释
共享对象的可见性
如果两个或多个线程共享一个对象,而没有正确使用volatile声明或同步,则一个线程对共享对象所做的更新可能对其他线程不可见。
假设共享对象最初存储在主内存中。然后,在CPU One上运行的线程将共享对象读取到其CPU缓存中。在那里,它对共享对象进行更改。只要CPU缓存没有刷新回主内存,其他CPU上运行的线程就看不到共享对象的更改版本。这样,每个线程最终可能会得到它自己的共享对象副本,每个副本都位于不同的CPU缓存中。
下图说明了草图情况。在左侧CPU上运行的一个线程将共享对象复制到其CPU缓存中,并将其count变量更改为2。在正确的CPU上运行的其他线程看不到此更改,因为计数更新尚未刷新回主内存。
为了解决这个问题,可以使用Java的易失性关键字。volatile关键字可以确保直接从主内存中读取给定的变量,并且在更新时总是写回主内存。
竞争条件
如果两个或多个线程共享一个对象,并且多个线程更新该共享对象中的变量,则可能发生争用条件。
假设线程A将共享对象的变量计数读取到其CPU缓存中。想象一下,线程B做的是相同的,但是进入了不同的CPU缓存。现在线程A加一个来计数,线程B也这样做。现在var1已经增加了两次,在每个CPU缓存中增加一次。
如果按顺序执行这些增量,变量计数将增加两次,并将原始值+2写回主内存。
但是,这两个增量是同时执行的,没有适当的同步。无论线程A和线程B中哪个线程将其更新版本的计数写回主内存,更新后的值将仅比原始值高1,尽管有两个增量。
此图说明了上述比赛条件下出现的问题:
为了解决这个问题,可以使用Java同步块。同步块保证在任何给定时间只有一个线程可以进入代码的给定关键部分。同步块还保证在同步块内访问的所有变量都将从主内存中读取,当线程退出同步块时,无论变量是否声明为volatile,所有更新的变量都将再次刷新回主内存。
DR:
多线程有三个主要问题:
1)比赛条件
2)缓存/过时内存
3)编译器和CPU优化
1)将此线程视为不安全代码:
虽然它看起来像一个操作,但实际上是3:从内存中读取x的当前值,向其中添加1,然后将其保存回内存。如果很少有线程同时尝试执行此操作,则操作的结果未定义。如果
在代码块上使用
1 2 3 | synchronized (this) { x++; // no problem now } |
把
2)此外,线程有自己的上下文——也就是说,它们可以从主内存缓存值。这意味着一些线程可以有一个变量的副本,但是它们在工作副本上操作,而不在其他线程之间共享该变量的新状态。
考虑在一个线程上,
将变量标记为
请注意,与数据竞争不同,过时内存不太容易(重新)生成,因为对主内存的刷新无论如何都会发生。
3)编译器和CPU可以(在线程之间没有任何形式的同步)将所有代码视为单线程。这意味着它可以查看一些代码,这在多线程方面是非常有意义的,并将其视为单线程的,而不是非常有意义的。因此,如果它不知道这个代码是为多线程设计的,那么它可以查看一个代码,为了优化,决定对它重新排序,甚至完全删除其中的一部分。
考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 | boolean b = false; int x = 10; void threadA() { x = 20; b = true; } void threadB() { if (b) { System.out.println(x); } } |
您可能认为,threadb只能打印20(如果在设置
注意,在Java 5新的内存模型之前,易失性没有解决这个问题。