今天我实验室的敏感操作完全错了.电子显微镜上的执行器越过它的边界,在一系列事件之后,我损失了1200万美元的设备.我已将故障模块中的40K以上线路缩小到:
import java.util.*;
class A {
static Point currentPos = new Point(1,2);
static class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) {
new Thread() {
void f(Point p) {
synchronized(this) {}
if (p.x+1 != p.y) {
System.out.println(p.x+" "+p.y);
System.exit(1);
}
}
@Override
public void run() {
while (currentPos == null);
while (true)
f(currentPos);
}
}.start();
while (true)
currentPos = new Point(currentPos.x+1, currentPos.y+1);
} …Run Code Online (Sandbox Code Playgroud) 我已经看了一下OpenJDK的源代码,CopyOnWriteArrayList似乎所有的写操作都受到同一个锁的保护,读操作根本就没有受到保护.据我所知,在JMM下,所有对变量(读取和写入)的访问都应该受到锁定或重新排序的影响.
例如,set(int, E)方法包含这些行(在锁定下):
/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);
Run Code Online (Sandbox Code Playgroud)
get(int)另一方面,该方法仅适用return get(getArray(), index);.
在我对JMM的理解中,这意味着get如果语句1-4被重新排序,如1-2(新)-4-2(copyOf)-3 ,则可能会观察到数组处于不一致状态.
我是否理解JMM不正确或是否有任何其他解释为什么CopyOnWriteArrayList线程安全?
在以下代码中:
class A {
private int number;
public void a() {
number = 5;
}
public void b() {
while(number == 0) {
// ...
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果调用方法b然后启动一个触发方法a的新线程,则方法b不能保证看到更改,number因此b可能永远不会终止.
当然,我们可以number volatile解决这个问题.但是出于学术原因,我们假设这volatile不是一个选择:
该JSR-133的常见问题告诉我们:
在我们退出synchronized块之后,我们释放了监视器,它具有将缓存刷新到主内存的效果,因此该线程所做的写操作对其他线程是可见的.在我们进入同步块之前,我们获取监视器,它具有使本地处理器高速缓存无效的效果,以便从主存储器重新加载变量.
这听起来像我只需要两个a并b进入和退出任何synchronized-Block,无论他们使用什么显示器.更确切地说,它听起来像这样......:
class A {
private int number;
public void a() {
number = 5;
synchronized(new Object()) {}
}
public void b() {
while(number == 0) {
// …Run Code Online (Sandbox Code Playgroud) 在Java Concurrency In Practice一书中,我们被告知可以通过编译器,JVM在运行时甚至由处理器重新排序程序的指令.因此,我们应该假设执行的程序不会以与我们在源代码中指定的顺序完全相同的顺序执行其指令.
但是,讨论Java内存模型的最后一章提供了一个先前发生的规则列表,指出了JVM保留了哪些指令排序.这些规则中的第一条是:
我相信"程序顺序"是指源代码.
我的问题:假设这个规则,我想知道什么指令可能实际重新排序.
"行动"定义如下:
Java内存模型是根据操作指定的,包括对变量的读取和写入,监视器的锁定和解锁,以及启动和连接线程.JMM定义了在程序中的所有操作之前调用的部分排序.为了保证执行动作B的线程可以看到动作A的结果(A和B是否出现在不同的线程中),必须在A和B之间的关系之前发生.在没有发生之前在两个之间进行排序操作,JVM可以随意重新排序.
其他提到的订单规则是:
必须是不可变对象的所有属性都是final?
据我说不.但我不知道,我是否正确.
我正在研究涉及大量并发编程的Android项目,我将实现一些自定义的线程间通信(java.util.concurent中的那个不适合我的目的).
并发编程一般不容易,但对Dalvik来说似乎更难.要获得正确的代码,您应该了解一些具体的事情以及Dalvik出现问题的地方.我找不到关于Dalvik VM的详细文档.大多数Android资源(甚至developer.android.com专注于平台API,并没有提供任何关于一些非平凡(或低级)事物的深层信息).
例如,Dalvik VM遵循哪个版本的Java语言规范?根据答案,volatile变量的处理是不同的,并影响使用volatile变量的任何并发代码.
已经有一些相关问题:
而fadden的一些答案非常有用,但我仍然希望对所讨论的问题有更详细和完整的理解.
所以在我感兴趣的原始问题之下(如果有必要,我将更新列表,因为之前的问题的答案将到达):
volatile变量语义的支持有多完整?在Android中的双重检查锁定中,fadden提供以下注释:
对.通过添加"volatile"关键字,这将适用于单处理器(所有Android版本)和SMP(3.0"蜂窝"及更高版本)
这是否意味着拥有双核CPU但只有Android 2.3的三星Galaxy SII可能会错误地执行并发代码?(当然Galaxy只是一个例子,关于任何具有Android 3.0前平台的多核设备的问题)
在Is Dalvik的内存模型中与Java相同吗?该法登提供下列句子的答案:
目前没有发布的Dalvik版本与JSR-133完全正确
这是否意味着任何现有的正确并发Java代码在发布此评论的任何Android版本上可能无法正常工作?
@gnat发表评论:
@Alexey Dalvik不符合任何JLS版本,因为一致性要求通过JCK,这不是Dalvik的选项.这是否意味着您甚至无法应用标准Java编译器,因为它符合标准规范?那有关系吗?如果有,怎么样?
好吧,我的问题有些含糊不清.我实际上的意思是JLS不仅是Java编译器实现的规则,而且是任何JVM实现的隐含指南.实际上,例如,JLS声明某些类型的读写是原子操作.编译器编写器不是很有趣,因为读/写只是转换成单个操作码.但是对于任何应该正确实现这些操作码的JVM实现来说都是必不可少的.现在你应该看看我在说什么.虽然Dalvik接受并执行使用标准Java编译器编译的程序,但没有任何保证 …
下面的代码(Java Concurrency in Practice清单16.3)由于显而易见的原因而不是线程安全的:
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
}
}
Run Code Online (Sandbox Code Playgroud)
但是,几页之后,在第16.3节中,他们指出:
UnsafeLazyInitialization如果Resource是不可变的,实际上是安全的.
我不明白这句话:
Resource是不可变的,那么观察resource变量的任何线程都将看到它为null或完全构造(由于Java内存模型提供的最终字段的强保证)resource可以重新排序(在一个读取if和一个读取return).因此,线程可以resource在if条件中看到非null 但返回空引用(*).我认为UnsafeLazyInitialization.getInstance()即使Resource是不可变的也可以返回null .是这样的,为什么(或为什么不)?
(*)为了更好地理解我关于重新排序的观点,Jeremy Manson的博客文章是JLS并发的第17章的作者之一,解释了如何通过良性数据竞争安全地发布String的哈希码以及如何删除使用局部变量可能导致哈希码错误地返回0,因为可能的重新排序非常类似于我上面描述的:
我在这里做的是添加一个额外的读取:在返回之前的第二次读取哈希.听起来很奇怪,并且不太可能发生,第一次读取可以返回正确计算的散列值,第二次读取可以返回0!这在内存模型下是允许的,因为该模型允许对操作进行大量重新排序.第二次读取实际上可以在您的代码中移动,以便您的处理器在第一次读取之前完成!
但是,除了相互排斥之外,还有更多的同步.同步确保线程在同步块之前或期间的内存写入以可预测的方式显示给在同一监视器上同步的其他线程.在我们退出synchronized块之后,我们释放了监视器,它具有将缓存刷新到主内存的效果,因此该线程所做的写操作对其他线程是可见的.在我们进入同步块之前,我们获取监视器,它具有使本地处理器高速缓存无效的效果,以便从主存储器重新加载变量.然后,我们将能够看到前一版本中显示的所有写入.
我还记得在现代Sun VM上阅读,无竞争同步很便宜.这个说法我有点困惑.考虑以下代码:
class Foo {
int x = 1;
int y = 1;
..
synchronized (aLock) {
x = x + 1;
}
}
Run Code Online (Sandbox Code Playgroud)
对x的更新需要同步,但是锁的获取是否也从缓存中清除了y的值?我无法想象会出现这种情况,因为如果确实如此,锁定条带化等技术可能无济于事.或者,JVM可以可靠地分析代码以确保使用相同的锁在另一个同步块中不修改y,因此在进入同步块时不会在缓存中转储y的值吗?
重要编辑我知道在发生两个赋值的线程中 "发生在之前" 我的问题是,另一个线程是否可能读取"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) 新的c ++标准引入了内存模型的概念.关于它的问题已经有了问题,它是什么意思,它如何改变我们用c ++编写代码的方式等等.
我有兴趣了解C++内存模型与旧的,众所周知的Java内存模型(1.5)的关系.它是一样的吗?它是相似的吗?他们有什么重大差异吗?如果是这样,为什么?
java内存模型已经存在很长时间了很多人都知道它非常不错,所以我想通过比较它来学习C++内存模型可能会有所帮助.
java ×9
concurrency ×6
android ×1
c++ ×1
c++11 ×1
dalvik ×1
final ×1
immutability ×1
jvm ×1
memory-model ×1
volatile ×1