同步关键字在内部如何工作 [英] how synchronized keyword works internally

查看:77
本文介绍了同步关键字在内部如何工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我阅读了以下程序并在博客中答复.

I read the below program and answer in a blog.

int x = 0;
boolean bExit = false;

线程1(未同步)

x = 1; 
bExit = true;

线程2(未同步)

if (bExit == true) 
System.out.println("x=" + x);

线程2是否可以打印"x=0"?
答案:是(原因:每个线程都有自己的变量副本.)

is it possible for Thread 2 to print "x=0"?
Ans : Yes ( reason : Every thread has their own copy of variables. )

您如何解决?
答案:通过使用使两个线程在公共互斥锁上同步或使两个变量均为volatile.

how do you fix it?
Ans: By using make both threads synchronized on a common mutex or make both variable volatile.

我的疑问是::如果我们将2变量设置为volatile,那么2个线程将共享主内存中的变量.这是有道理的,但是在同步的情况下,由于两个线程都有自己的变量副本,因此将如何解决该问题.

My doubt is : If we are making the 2 variable as volatile then the 2 threads will share the variables from the main memory. This make a sense, but in case of synchronization how it will be resolved as both the thread have their own copy of variables.

请帮助我.

推荐答案

这实际上比看起来要复杂.有一些不可思议的事情在起作用.

This is actually more complicated than it seems. There are several arcane things at work.

缓存

说每个线程都有自己的变量副本"并不完全正确.每个线程可能有其自己的变量副本,它们可能会也可能不会将这些变量刷新到共享内存中和/或从中读取它们,因此整个过程是不确定的.而且,术语 flushing 实际上是依赖于实现的.有严格的术语,例如内存一致性,先发生顺序同步顺序.

Saying "Every thread has their own copy of variables" is not exactly correct. Every thread may have their own copy of variables, and they may or may not flush these variables into the shared memory and/or read them from there, so the whole thing is non-deterministic. Moreover, the very term flushing is really implementation-dependent. There are strict terms such as memory consistency, happens-before order, and synchronization order.

重新排序

这是一个更加神秘的东西.这个

This one is even more arcane. This

x = 1; 
bExit = true;

甚至不能保证线程1首先将1写入x,然后将true写入bExit.实际上,它甚至不保证所有这些都会发生.如果以后不使用某些值,则编译器可能会优化掉某些值.编译器和CPU也可以按照他们想要的任何方式对指令进行重新排序,只要结果与如果一切按程序顺序所发生的情况是不可区分的即可.也就是说,对于当前线程来说是无法区分的!直到……之前,没人在乎其他线程.

does not even guarantee that Thread 1 will first write 1 to x and then true to bExit. In fact, it does not even guarantee that any of these will happen at all. The compiler may optimize away some values if they are not used later. The compiler and CPU are also allowed to reorder instructions any way they want, provided that the outcome is indistinguishable from what would happen if everything was really in program order. That is, indistinguishable for the current thread! Nobody cares about other threads until...

出现同步

同步不仅意味着对资源的独占访问.这还不仅仅是防止线程相互干扰.这也与内存障碍有关.可以粗略地描述为每个同步块在入口和出口处都有不可见的指令,第一个说从共享内存中读取所有内容,以使它们尽可能最新",最后一个说现在刷新所有内容".一直在做共享内存".我之所以说大致"是因为,同样,整个事情都是实现细节.内存障碍也限制了重新排序:操作可能仍会重新排序,但是退出同步块后出现在共享内存中的结果必须与如果一切确实按程序顺序发生的情况相同.

Synchronization does not only mean exclusive access to resources. It is also not just about preventing threads from interfering with each other. It's also about memory barriers. It can be roughly described as each synchronization block having invisible instructions at the entry and exit, the first one saying "read everything from the shared memory to be as up-to-date as possible" and the last one saying "now flush whatever you've been doing there to the shared memory". I say "roughly" because, again, the whole thing is an implementation detail. Memory barriers also restrict reordering: actions may still be reordered, but the results that appear in the shared memory after exiting the synchronized block must be identical to what would happen if everything was indeed in program order.

当然,只有当两个块都使用相同的锁定对象时,所有这些方法才起作用.

All that only works, of course, only if both blocks use the same locking object.

第章中详细介绍了整个过程JLS的第17条.特别重要的是所谓的先于订单".如果您在文档中看到此发生在之前",则意味着执行"that"的任何人都可以看到第一个线程在"this"之前执行的所有操作.这甚至可能不需要任何锁定.并发集合是一个很好的例子:一个线程放置了一个东西,另一个线程读取了这个东西,并且神奇地保证了第二个线程将看到第一个线程在将该对象放入集合之前所做的一切,即使这些动作与该对象无关.集合本身!

The whole thing is described in details in Chapter 17 of the JLS. In particular, what's important is the so-called "happens-before order". If you ever see in the documentation that "this happens-before that", it means that everything the first thread does before "this" will be visible to whoever does "that". This may even not require any locking. Concurrent collections are a good example: one thread puts there something, another one reads that, and that magically guarantees that the second thread will see everything the first thread did before putting that object into the collection, even if those actions had nothing to do with the collection itself!

易变变量

最后一个警告:您最好放弃让变量volatile可以解决问题的想法.在这种情况下,使bExit易失性就足够了,但是麻烦太多了,以至于使用易失性可能导致我什至不愿对此进行讨论.但是可以肯定的是:使用synchronized的效果比使用volatile的效果要强得多,这对于记忆效果也是如此.更糟糕的是,volatile语义在某些Java版本中发生了变化,因此可能存在一些仍使用旧语义的版本,这些语义更加晦涩和混乱,而synchronized始终可以很好地工作,前提是您了解它的含义和用法.

One last warning: you better give up on the idea that making variables volatile will solve things. In this case maybe making bExit volatile will suffice, but there are so many troubles that using volatiles can lead to that I'm not even willing to go into that. But one thing is for sure: using synchronized has much stronger effect than using volatile, and that goes for memory effects too. What's worse, volatile semantics changed in some Java version so there may exist some versions that still use the old semantics which was even more obscure and confusing, whereas synchronized always worked well provided you understand what it is and how to use it.

使用volatile的唯一唯一原因是性能,因为synchronized可能会导致锁争用和其他麻烦.阅读《 Java并发实践》以了解所有内容.

Pretty much the only reason to use volatile is performance because synchronized may cause lock contention and other troubles. Read Java Concurrency in Practice to figure all that out.

问与答; A

1)您写了现在刷新您在共享库中所做的任何事情 关于同步块的内存".但是我们只会看到变量 我们在同步块中访问的内容或 进行线程调用同步(即使对于未在线程中访问的变量也是如此) 同步块)?

1) You wrote "now flush whatever you've been doing there to the shared memory" about synchronized blocks. But we will see only the variables that we access in the synchronize block or all the changes that the thread call synchronize made (even on the variables not accessed in the synchronized block)?

简短的回答:它将刷新"在同步块期间或进入同步块之前更新的所有变量.再说一次,因为刷新是实现的细节,所以您甚至不知道它会真正刷新某件事还是做一些完全不同的事情(或者根本不做任何事情,因为实现和特定情况已经以某种方式保证了它会工作).

Short answer: it will "flush" all variables that were updated during the synchronized block or before entering the synchronized block. And again, because flushing is an implementation detail, you don't even know whether it will actually flush something or do something entirely different (or doesn't do anything at all because the implementation and the specific situation already somehow guarantee that it will work).

在同步块内部未访问的变量显然在块执行期间不会更改.但是,例如,如果您在进入同步块之前更改了其中一些变量,则这些更改与同步块中发生的任何事情之间具有先发生后关系(

Variables that wasn't accessed inside the synchronized block obviously won't change during the execution of the block. However, if you change some of those variables before entering the synchronized block, for example, then you have a happens-before relationship between those changes and whatever happens in the synchronized block (the first bullet in 17.4.5). If some other thread enters another synchronized block using the same lock object then it synchronizes-with the first thread exiting the synchronized block, which means that you have another happens-before relationship here. So in this case the second thread will see the variables that the first thread updated prior to entering the synchronized block.

如果第二个线程尝试在不同步同一锁的情况下读取这些变量,则不能保证看到更新.但是话又说回来,也不能保证在同步块内也看到更新.但这是因为第二个线程中没有内存读取屏障,而不是因为第一个线程没有刷新"其变量(内存写入屏障).

If the second thread tries to read those variables without synchronizing on the same lock, then it is not guaranteed to see the updates. But then again, it isn't guaranteed to see the updates made inside the synchronized block as well. But this is because of the lack of the memory-read barrier in the second thread, not because the first one didn't "flush" its variables (memory-write barrier).

2)在本章中,您发布了(JLS)的内容: volatile字段(第8.3.1.4节)发生在每次后续读取该字段之前 字段."这并不意味着当变量为volatile时,您将 只看到它的变化(因为它是书面的,所以写发生在-before 读取,而不是发生在它们之间的每项操作之前!).我是说 这并不意味着在示例中, 问题,我们可以看到bExit = true,但是在第二个线程中x = 0 只有bExit易变吗?我问,因为我在这里找到了这个问题:

2) In this chapter you post (of JLS) it is written that: "A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field." Doesn't this mean that when the variable is volatile you will see only changes of it (because it is written write happens-before read, not happens-before every operation between them!). I mean doesn't this mean that in the example, given in the description of the problem, we can see bExit = true, but x = 0 in the second thread if only bExit is volatile? I ask, because I find this question here: http://java67.blogspot.bg/2012/09/top-10-tricky-java-interview-questions-answers.html and it is written that if bExit is volatile the program is OK. So the registers will flush only bExits value only or bExits and x values?

根据与Q1中相同的推理,如果在bExit = true x = 1之后执行bExit = true ,则由于程序顺序,存在线程内事前发生关系.现在,由于易失性写入发生在易失性读取之前,因此可以保证第二个线程将看到在将true写入bExit之前第一个线程更新的内容.请注意,此行为仅是从Java 1.5左右开始的,因此较旧的或错误的实现可能会或可能不支持此行为.我已经在使用此功能的标准Oracle实现中看到了一些东西(java.concurrent集合),因此您至少可以假定它在此起作用.

By the same reasoning as in Q1, if you do bExit = true after x = 1, then there is an in-thread happens-before relationship because of the program order. Now since volatile writes happen-before volatile reads, it is guaranteed that the second thread will see whatever the first thread updated prior to writing true to bExit. Note that this behavior is only since Java 1.5 or so, so older or buggy implementations may or may not support this. I have seen bits in the standard Oracle implementation that use this feature (java.concurrent collections), so you can at least assume that it works there.

3)为什么使用有关内存的同步块时监视很重要 能见度?我的意思是当尝试退出同步块时还不是全部 变量(我们在此块中访问的变量或 线程-这与第一个问题有关)从寄存器刷新 到主内存还是广播到所有CPU缓存?为什么反对 同步有关系吗?我简直无法想象什么是关系, 如何制作它们(在同步对象和内存之间). 我知道我们应该使用同一台显示器来查看此更改,但是我 不明白应该如何将可见的内存映射到 对象.抱歉,很长的问题,但这确实是 对我来说很有趣的问题,它与问题有关(我 会针对此入门问题发布确切的问题.

3) Why monitor matters when using synchronized blocks about memory visibility? I mean when try to exit synchronized block aren't all variables (which we accessed in this block or all variables in the thread - this is related to the first question) flushed from registers to main memory or broadcasted to all CPU caches? Why object of synchronization matters? I just cannot imagine what are relations and how they are made (between object of synchronization and memory). I know that we should use the same monitor to see this changes, but I don't understand how memory that should be visible is mapped to objects. Sorry, for the long questions, but these are really interesting questions for me and it is related to the question (I would post questions exactly for this primer).

哈,这个真的很有趣.我不知道. 可能无论如何都会刷新,但是考虑到Java规范是高度抽象的,因此,它可能允许某些确实很怪异的硬件,其中可能存在部分刷新或其他类型的内存障碍.假设您有一台两CPU的计算机,每个CPU上有2个内核.每个CPU的每个内核都有一些本地缓存,还有一个公共缓存.一个真正智能的VM可能希望在一个CPU上调度两个线程,在另一个CPU上调度两个线程.每对线程都使用自己的监视器,VM会检测到由这两个线程修改的变量未在其他任何线程中使用,因此只会将其刷新到CPU本地缓存为止.

Ha, this one is really interesting. I don't know. Probably it flushes anyway, but Java specification is written with high abstraction in mind, so maybe it allows for some really weird hardware where partial flushes or other kinds of memory barriers are possible. Suppose you have a two-CPU machine with 2 cores on each CPU. Each CPU has some local cache for every core and also a common cache. A really smart VM may want to schedule two threads on one CPU and two threads on another one. Each pair of the threads uses its own monitor, and VM detects that variables modified by these two threads are not used in any other threads, so it only flushes them as far as the CPU-local cache.

另请参阅有关同一问题的问题.

4)我认为编写volatile之前的一切都取决于 我们读它的日期(此外,当我们使用volatile时, Java是内存障碍),但是文档中没有这么说.

4) I thought that everything before writing a volatile will be up to date when we read it (moreover when we use volatile a read that in Java it is memory barrier), but the documentation don't say this.

它确实:

17.4.5. 如果x和y是同一线程的动作,并且x在程序顺序上位于y之前,则为hb(x,y).

17.4.5. If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

如果hb(x,y)和hb(y,z),则hb(x,z).

If hb(x, y) and hb(y, z), then hb(x, z).

在随后的每个后续操作之前,都会对volatile字段(第8.3.1.4节)进行写操作 阅读该字段.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

如果按程序顺序x = 1bExit = true之前,那么我们在它们之间发生-before.如果之后有其他线程读取bExit,则发生在写入和读取之间.并且由于传递性,我们也发生在x = 1和第二个线程读取bExit之前.

If x = 1 comes before bExit = true in program order, then we have happens-before between them. If some other thread reads bExit after that, then we have happens-before between write and read. And because of the transitivity, we also have happens-before between x = 1 and read of bExit by the second thread.

5)另外,如果我们有不稳定的Person p,我们会有一定的依赖性 当我们使用p.age = 20并打印(p.age)或我们在 这种情况下(假设年龄不可变)? -我想-不

5) Also, if we have volatile Person p does we have some dependency when we use p.age = 20 and print(p.age) or have we memory barrier in this case(assume age is not volatile) ? - I think - No

您是正确的.由于age不易失,因此没有内存障碍,这是最棘手的事情之一.例如,这是来自CopyOnWriteArrayList的片段:

You are correct. Since age is not volatile, then there is no memory barrier, and that's one of the trickiest things. Here is a fragment from CopyOnWriteArrayList, for example:

        Object[] elements = getArray();
        E oldValue = get(elements, index);
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);

在这里,getArraysetArray对于array字段来说是微不足道的设置者和获取者.但是由于代码更改了数组的元素,因此有必要将对数组的引用写回到它的原始位置,以使对数组元素的更改变得可见.请注意,即使要替换的元素与最初存在的元素相同,也可以这样做!正是因为该元素的某些字段可能已被调用线程更改,所以有必要将这些更改传播给将来的读者.

Here, getArray and setArray are trivial setter and getter for the array field. But since the code changes elements of the array, it is necessary to write the reference to the array back to where it came from in order for the changes to the elements of the array to become visible. Note that it is done even if the element being replaced is the same element that was there in the first place! It is precisely because some fields of that element may have changed by the calling thread, and it's necessary to propagate these changes to future readers.

6)并且在随后两次读取volatile之前是否发生任何情况 场地?我的意思是二读会看到线程中的所有更改 它会先读取此字段(当然,我们只会进行更改 如果波动影响了所有变更的可见性-我就是 有点糊涂,是否正确)?

6) And is there any happens before 2 subsequent reads of volatile field? I mean does the second read will see all changes from thread which reads this field before it(of course we will have changes only if volatile influence visibility of all changes before it - which I am a little confused whether it is true or not)?

否,易失性读取之间没有关系.当然,如果一个线程执行易失性写入,然后另外两个线程执行易失性读取,则可以保证它们至少看到与易失性写入之前一样的最新信息,但不能保证一个线程是否会看到更多内容.最新值.而且,甚至没有一个易读发生在另一个之前的严格定义!认为在单个全球时间轴上发生的一切都是错误的.它更像是具有独立时间轴的并行Universe,有时通过执行同步和与内存屏障交换数据来同步其时钟.

No, there is no relationship between volatile reads. Of course, if one thread performs a volatile write and then two other thread perform volatile reads, they are guaranteed to see everything at least up to date as it was before the volatile write, but there is no guarantee of whether one thread will see more up-to-date values than the other. Moreover, there is not even strict definition of one volatile read happening before another! It is wrong to think of everything happening on a single global timeline. It is more like parallel universes with independent timelines that sometimes sync their clocks by performing synchronization and exchanging data with memory barriers.

这篇关于同步关键字在内部如何工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆