我对线程概念还很陌生,并尝试更多地了解它.最近,我发现了一篇关于Jeremy Manson撰写的" Java中的易失手段"的博客文章,他写道:
当一个线程写入一个volatile变量,而另一个线程看到该写入时,第一个线程告诉第二个线程关于内存的所有内容,直到它执行对该volatile变量的写入.[...] 所有的由线程1看到的存储内容,才写信给
[volatile] ready,必须是可见的主题2,它读取值后true进行ready.[自己强调]
现在,这是否意味着在写入volatile变量时,线程1的内存中保存的所有变量(volatile或not)将在读取volatile变量后变为可见?如果是这样,是否可以从官方Java文档/ Oracle源代码中将该语句拼凑起来?从哪个版本的Java开始这个工作?
特别是,如果所有线程共享以下类变量:
private String s = "running";
private volatile boolean b = false;
Run Code Online (Sandbox Code Playgroud)
并且线程1首先执行以下操作:
s = "done";
b = true;
Run Code Online (Sandbox Code Playgroud)
然后线程2执行(在线程1写入volatile字段之后):
boolean flag = b; //read from volatile
System.out.println(s);
Run Code Online (Sandbox Code Playgroud)
这会保证打印"完成"吗?
如果我将写入和读取放入块中而不是声明b,会发生什么?volatilesynchronized
另外,在题为" 线程之间是否共享静态变量? "的讨论中,@ TREE 写道:
不要使用volatile来保护多个共享状态.
为什么?(对不起;我还没有就其他问题发表评论,或者我会在那里问过......)
重要编辑我知道在发生两个赋值的线程中 "发生在之前" 我的问题是,另一个线程是否可能读取"b"非空,而"a"仍为空.所以我知道如果你从与之前调用setBothNonNull(...)的线程相同的线程调用doIt(),那么它就不能抛出NullPointerException.但是如果一个人从另一个线程调用doIt()而不是调用setBothNonNull(...)的那个呢?
请注意,这个问题仅仅是对volatile关键字和volatile保证:这是不是对synchronized关键字(所以请不要回答"您必须使用同步"因为我没有任何问题要解决:我只是想了解volatile担保关于无序执行(或缺乏保证).
假设我们有一个包含两个volatileString引用的对象,它们被构造函数初始化为null,并且我们只有一种方法来修改两个String:通过调用setBoth(...)并且我们之后只能将它们的引用设置为非null引用(只允许构造函数将它们设置为null).
例如(这只是一个例子,毫无疑问):
public class SO {
private volatile String a;
private volatile String b;
public SO() {
a = null;
b = null;
}
public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
a = one;
b = two;
}
public String getA() …Run Code Online (Sandbox Code Playgroud) 考虑一下volatile int sharedVar.我们知道JLS为我们提供了以下保证:
w其写入值之前i以sharedVar程序顺序happens-before写入动作;i通过w happens-before的成功读取i从sharedVar由读取线程r;i从sharedVar由读线程r happens-before的所有后续行动r的程序顺序.然而,仍有给出没有挂钟时间的保证,当读线程将观察值i.一个完全不会让读取线程看到该值的实现仍然符合此契约.
我已经考虑了一段时间,我看不到任何漏洞,但我认为必须有.请指出我的推理漏洞.
我有两个主题:
主题:1
a = 1;
x = b;
Run Code Online (Sandbox Code Playgroud)
主题:2
b = 1
y = a
Run Code Online (Sandbox Code Playgroud)
这里a和b被声明为volatile.我不明白在a = 1之间如何创建"before-before"边缘; 和y = a; 在x = b之间; 和b = 1;
我知道通过使用volatile变量可以防止从线程缓存中读取过时值.但是,在订购之前,如何确保变量发生变化.
具体来说,我不明白这一点:
对每个后续读取相同字段之前发生对易失性字段的写入.
锄头有用吗?
有多个代码示例假定以下指令(1)和(2)不能重新排序:
int value;
volatile boolean ready;
// ...
value = 1; // (1)
ready = true; // (2)
Run Code Online (Sandbox Code Playgroud)
后一个 Stack Overflow 答案是指 JLS §17.4.5:
如果 x 和 y 是同一线程的动作,并且 x 在程序顺序中排在 y 之前,则 hb(x, y)。
但是我不明白为什么这应该适用于这里,因为 JLS示例 17.4-1还指出:
[...] 允许编译器重新排序任一线程中的指令,前提是这不会影响该线程的单独执行。
这显然是这里的情况。
JLS 中特定于的所有其他定义volatile仅针对相同的 volatile 变量,而不针对其他操作:
对 volatile 字段(第 8.3.1.4 节)的写入发生在该字段的每次后续读取之前。
在人们看到 volatile (读或写)的使用可能不会重新排序的保证时,我感到困惑。
您能否将您的解释基于 JLS 或基于 JLS 的其他来源。
我有一个大的数据映射(HashMap),保存在内存中,并由后台线程进行增量更新(基于传入消息):
<KEY> => <VALUE>
...
Run Code Online (Sandbox Code Playgroud)
最终用户将通过REST API查询它:
GET /lookup?key=<KEY>
Run Code Online (Sandbox Code Playgroud)
一旦收到特殊控制消息,即不会立即应用更新,而是分批进行更新,即
MESSAGE: "Add A"
A=<VALUE> //Not visible yet
MESSAGE: "Add B"
B=<VALUE> //Not visible yet
MESSAGE: "Commit"
//Updates are now visible to the end-users
A=<VALUE>
B=<VALUE
Run Code Online (Sandbox Code Playgroud)
我设计的架构如下:
<KEY> => <VALUE>
...
Run Code Online (Sandbox Code Playgroud)
将write-> read写入volatile字段会在边缘之前发生:
在随后每次对该字段进行读取之前,都会写入一个易失字段(第8.3.1.4节)。
https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
并且正确选择了宽限期,我希望在交换之后,最终用户请求(一次全部)将看到对任何应用到PassiveCopy(通过putAll())的更新。
确实是这样,还是有什么极端情况会使这种方法失败?
注意
我知道创建Map的副本(以便每次都将一个新的Map实例分配给activeCopy)是安全的,但是我不想这样做(因为它确实很大)。