gpt4 book ai didi

java - 同步关键字在内部如何工作

转载 作者:行者123 更新时间:2023-12-03 23:09:16 25 4
gpt4 key购买 nike

我阅读了以下程序并在博客中回答。

int x = 0;
boolean bExit = false;

线程1(未同步)
x = 1; 
bExit = true;

线程2(未同步)
if (bExit == true) 
System.out.println("x=" + x);

线程2是否可以打印“ x=0”?
Ans :是(原因:每个线程都有自己的变量副本。)

您如何解决?
Ans :通过使用使两个线程在公共(public)互斥锁上同步或使两个变量均为volatile。

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

请帮我。

最佳答案

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

缓存

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

重新排序

这是一个更神秘的东西。这

x = 1; 
bExit = true;

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

同步出现在

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

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

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

易变变量

最后警告:您最好放弃让 volatile变量解决问题的想法。在这种情况下,将 bExit设置为volatile可能就足够了,但是麻烦太多了,使用volatile可能会导致我什至不愿讨论。但是可以肯定的是:使用 synchronized的效果比使用 volatile的效果要强得多,这对内存效果也很重要。更糟糕的是, volatile语义在某些Java版本中发生了变化,因此可能存在一些仍使用旧语义的版本,这些语义更加晦涩难懂,而 synchronized在您了解它是什么以及如何使用的情况下始终可以很好地工作。

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

问答

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)?



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

在同步块(synchronized block)内部未访问的变量显然在块执行期间不会更改。但是,例如,如果您在进入同步块(synchronized block)之前更改了其中一些变量,则这些更改与同步块(synchronized block)中发生的任何事情之间都具有事前-事前的关系( 17.4.5中的第一个项目符号)。如果某个其他线程使用相同的锁定对象进入另一个同步块(synchronized block),则它将与退出同步块(synchronized block)的第一个线程进行同步,这意味着您在此处具有另一个“事前发生”关系。因此,在这种情况下,第二个线程将看到第一个线程在进入同步块(synchronized block)之前更新的变量。

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

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,则由于程序顺序,存在线程内事前发生关系。现在,由于 volatile 写入发生在 volatile 读取之前,因此可以保证第二个线程将看到在将 true写入 bExit之前第一个线程更新的内容。请注意,此行为仅是从Java 1.5左右开始的,因此较旧的或错误的实现可能会或可能不支持此行为。我已经在使用此功能的标准Oracle实现中看到了一些东西(java.concurrent集合),因此您至少可以假定它在此起作用。

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的每个内核都有一些本地缓存,还有一个公共(public)缓存。一个真正智能的VM可能希望在一个CPU上调度两个线程,在另一个CPU上调度两个线程。每对线程使用其自己的监视器,并且VM检测到由这两个线程修改的变量未在其他任何线程中使用,因此它仅将其刷新到CPU本地缓存为止。

另请参阅 this question关于同一问题。

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. If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

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

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之间之前。

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的片段:
        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);

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

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)?



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

关于java - 同步关键字在内部如何工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34675937/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com