我想确保根据Java内存模型正确理解'有效不可变对象'的行为.
假设我们有一个可变类,我们希望将其发布为有效的不可变类:
class Outworld {
// This MAY be accessed by multiple threads
public static volatile MutableLong published;
}
// This class is mutable
class MutableLong {
private long value;
public MutableLong(long value) {
this.value = value;
}
public void increment() {
value++;
}
public long get() {
return value;
}
}
Run Code Online (Sandbox Code Playgroud)
我们执行以下操作:
// Create a mutable object and modify it
MutableLong val = new MutableLong(1);
val.increment();
val.increment();
// No more modifications
// UPDATED: Let's say for this example we …Run Code Online (Sandbox Code Playgroud) 我有这个类,我在那里使用它们来缓存实例并克隆它们(数据是可变的).
我想知道我是否可以面对这个重新排序的问题.
我已经看过这个答案和JLS,但我仍然没有信心.
public class DataWrapper {
private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
private Data data;
private String name;
public static DataWrapper getInstance(String name) {
DataWrapper instance = map.get(name);
if (instance == null) {
instance = new DataWrapper(name);
}
return instance.cloneInstance();
}
private DataWrapper(String name) {
this.name = name;
this.data = loadData(name); // A heavy method
map.put(name, this); // I know
}
private DataWrapper cloneInstance() {
return new DataWrapper(this);
}
private DataWrapper(DataWrapper that) {
this.name …Run Code Online (Sandbox Code Playgroud) "Java Concurrency in Practice"给出了以下不安全类的示例,由于Java内存模型的性质可能最终会永久运行或打印0.
这个类试图证明的问题是这里的变量不是线程之间的"共享".因此,线程看到的值可能与另一个线程不同,因为它们不是易失性或同步的.另外由于JVM ready = true允许的语句的重新排序可能在number = 42之前设置.
对我来说,这个类总是使用JVM 1.6正常工作.有关如何让这个类执行不正确的行为(即打印0或永远运行)的任何想法?
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
Run Code Online (Sandbox Code Playgroud) 这个问题仅涉及内存可见性,不会发生在之前和发生之后.Java中有四种方法可以保证一个线程中的内存更改对另一个线程可见.(参考http://gee.cs.oswego.edu/dl/cpj/jmm.html)
根据Java Concurrency in Practice,关于这些问题的圣经:
volatile变量的可见性效果超出了volatile变量本身的值.当线程甲写入到易失性可变,并随后线程乙读取相同的变量,的值的所有变量都是对可见光甲于写入所述易失性可变变得可见之前B看书挥发性变量之后.
这是否意味着JVM实际上跟踪了易失性变量读写,以便知道如何将内存从A刷新到B而不是A到C?所以A写入变量,后来C从变量读取,然后B从变量中读取,刷新是在A和B以及A和C之间以每线程完成的,而不是 B和C?或者,它是否暗示所有缓存的内存都被刷新,无论线程如何?是只刷新的volatile变量,还是所有缓存的内存?
对于synchronized关键字刷新,它表示只有锁内部更新的内存才能保证发布到其他线程.这意味着在下面的代码中,两个线程在运行method(),同步块将刷新staticVar2到另一个线程,但不是 staticVar1,这是正确的吗?
此外,如果另一个线程正在执行method2(),同步differentLock可以导致发生 - 在发生之前 - 发生问题method().但是,问题在于可见性.如果线程A执行method,那么后面的线程B执行method2() …
注意:此问题与volatile,AtomicLong或所描述的用例中的任何感知缺陷无关.
鉴于以下内容:
- 最近的64位OpenJDK 7/8(最好7位,但8位也很有帮助)
- 多处理英特尔基础系统
- 非易失性长原始变量
- 多个不同步的mutator线程
- 一个不同步的观察者线程
观察者是否始终保证会遇到由变异线程写的完整值,或者是撕裂危险的单词?
此属性对于32位基元和64位对象引用是存在的,但是对于long和double,JLS不保证:
17.7.非原子对double和long的处理:
出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位一半写入一次.这可能导致线程从一次写入看到64位值的前32位,而从另一次写入看到第二次32位的情况.
但是抱着你的马:
[...]为了效率,这种行为是特定于实现的; Java虚拟机的实现可以自由地以原子方式或分两部分执行对long和double值的写入.鼓励Java虚拟机的实现避免在可能的情况下拆分64位值.[...]
因此,JLS 允许 JVM实现拆分64位写入,并鼓励开发人员相应地进行调整,但也鼓励 JVM实现者坚持使用64位写入.我们还没有回答最新版本的HotSpot.
由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,我试图分析JIT编译的实际汇编输出.长话短说:需要进一步测试,但我只能在long上看到原子64位操作.
我使用了hdis,一个OpenJDK的反汇编插件.在我老化的OpenJDK 7u25版本中构建并安装了插件之后,我开始编写一个简短的程序:
public class Counter {
static long counter = 0;
public static void main(String[] _) {
for (long i = (long)1e12; i < (long)1e12 + 1e5; i++)
put(i);
System.out.println(counter);
}
static void put(long v) {
counter += v;
}
}
Run Code Online (Sandbox Code Playgroud)
我确保始终使用大于MAX_INT(1e12到1e12 + 1e5)的值,并重复操作足够的次数(1e5)以触发JIT.
编译后,我用hdis执行Counter.main(),如下所示:
java -XX:+UnlockDiagnosticVMOptions \
-XX:PrintAssemblyOptions=intel \
-XX:CompileCommand=print,Counter.put …Run Code Online (Sandbox Code Playgroud) 我正在尝试理解Java 发生 - 在订单概念之前,有一些东西看起来很混乱.据我所知,之前发生的只是行动集上的一个订单,并没有提供有关实时执行订单的任何保证.实际上(强调我的):
应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生.如果重新排序产生的 结果与合法执行一致,则不是非法的.
因此,所有它说的是,如果有两个动作w(写)和r(读),使得HB(W,R) ,较r 可能实际发生之前,w在执行,但不能保证它会的.w读取也会观察到写入r.
我如何确定在运行时随后执行两个操作?例如:
public volatile int v;
public int c;
Run Code Online (Sandbox Code Playgroud)
操作:
Thread A
v = 3; //w
Thread B
c = v; //r
Run Code Online (Sandbox Code Playgroud)
这里我们有,hb(w, r)但这并不意味着在分配后c会包含价值3.如何强制执行c3?同步订单是否提供此类保证?
在Java的内存模型保证了之前发生的对象的构造和释放的关系:
从对象的构造函数的末尾到该对象的终结器(第12.6节)的开始有一个发生前的边缘.
以及最终字段的构造函数和初始化:
当构造函数完成时,对象被认为是完全初始化的.在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值.
还有一个关于volatile字段的保证,因为关于对这些字段的所有访问的关系都存在:
写入易失性字段(第8.3.1.4节) - 在每次后续读取该字段之前发生.
但是常规的,古老的非易失性领域呢?我已经看到很多多线程代码在使用非易失性字段构造对象后不会创建任何类型的内存屏障.但是我从来没有见过或听说过任何问题,而且我自己也无法重建这种局部结构.
现代JVM在施工后是否只是放置了内存屏障?避免在施工周围重新排序?还是我很幸运?如果是后者,是否可以编写可以随意重现部分构造的代码?
编辑:
澄清一下,我说的是以下情况.假设我们有一个班级:
public class Foo{
public int bar = 0;
public Foo(){
this.bar = 5;
}
...
}
Run Code Online (Sandbox Code Playgroud)
一些Thread T1实例化一个新Foo实例:
Foo myFoo = new Foo();
然后将实例传递给其他线程,我们将调用它T2:
Thread t = new Thread(() -> {
if (myFoo.bar == 5){
....
}
});
t.start();
Run Code Online (Sandbox Code Playgroud)
T1 进行了两次我们感兴趣的写作:
bar了新实例化的值5myFoomyFoo变量对于T1,我们得到写#1 发生的保证- 在写#2 之前:
线程中的每个动作都发生 …
I'm reading Chapter 17. Threads and Locks of JLS and the following statement about sequential consistency in Java seems incorrect to me:
If a program has no data races, then all executions of the program will appear to be sequentially consistent.
They define a data race as:
When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.
They define conflicted accesses as:
Two accesses to (reads …
代码示例:
class Obj1 {
int f1 = 0;
}
volatile Obj1 v1;
Obj1 v2;
Thread 1 | Thread 2 | Thread 3
-------------------------------------------------
var o = new Obj1(); | |
o.f1 = 1; | |
v1 = o; | |
| v2 = v1; |
| | var r1 = v2.f1;
Is (r1 == 0) possible?
Run Code Online (Sandbox Code Playgroud)
这里的对象o:
Thread 1到Thread 2通过volatile字段v1Thread 2到Thread 3 …内存模型在 17.4 中定义。内存模型。
17.5 中给出了现场final多线程保证。最终字段语义。
我不明白为什么这些是单独的部分。
AFAIKfinal和内存模型都提供了一些保证。
任何真正的程序执行都必须遵守这两个保证。
但现在很清楚这些final保证是否适用于用于验证 17.4.8 中因果关系要求的中间执行。执行和因果关系要求。
另一个不清楚的时刻是17.5.1。Final Fields 的语义定义了一个新的“special” ,它与内存模型happens-before中的不同:happens-before
此happens-before排序不会与其他happens-before排序传递地关闭。
如果它们相同happens-before,则happens-before不再是偏序(因为它不具有传递性)。
我不明白这怎么不会破坏事情。
如果这些不同happens-before,那么就不清楚 17.5 中的是什么。Final Field Semantics确实如此。17.4中
的。内存模型用于限制读取可以返回的内容:happens-before
非正式地,如果没有happens-before排序来阻止
r读取,则允许读取查看写入的结果。w
但是17.5。最后的字段语义是一个不同的部分。