我试图理解Java中的并发编程是如何工作的。很多时候我会阅读不同的并发事物等等。但我不确定cachable
范围是什么。使用cachable
我的意思是线程可以缓存变量。
例如,如果我有自定义Thread
:
public class MyThread extends Thread {
private boolean isRunning;
public void setRunning(boolean running) {
isRunning = running;
}
public boolean getRunning(){
return isRunning;
}
@Override
public void run() {
while (isRunning){
// ... do something
}
}
}
因此我们假设我有以下main
方法:
public static void Main(String[] args) {
Person p = new Person();
final MyThread t1 = new MyThread();
final MyThread t2 = new MyThread();
Thread t3 = new Thread(()-> {
while(true){System.out.println(p.name);
System.out.println(t1.getRunning());
}});
t1.start();
t2.start();
t3.start();
}
让我们假设在此之后有一个代码可以更改此对象属性(但不是它们指向的引用!)。并且让我指出我要求缓存,而不是关于并发修改;
所以我的问题是:
1)t3
和p.name
可能会看到错误的值t1.isRunning
。 - >我想No
,因为我认为它可以缓存对这些对象的引用,所以如果我们更改p
点t3
的引用,可以看到错误的人参考,但在这种情况下它将缓存正确的引用,因此它将进入主存储器(RAM)并将获得正确的值。
2)可以t2
缓存t2.isRunning
(自己的isRunning
变量)吗?我应该同步setRunning
和getRunning
的使用情况吗?
例如,如果在t3
中我有这样的代码:t2.setRunning(false)
t2
可以t3
永久循环(如果只有t2.isRunning
中的此代码更改t2
) ?
当然,如果volatile
可以缓存自己的变量,我应该使用synchronize
或t2
。我认为cachable
可以缓存其isRunning。
那么我们怎样才能理解不同情况下/** Extending Javascript Array for function Contains */
Array.prototype.contains = function (element) {
return this.indexOf(element) > -1;
};
/** Set class "odd" to itself and run proceedEven() on all children with class "box" */
function proceedOdd(oddItem) {
oddItem.classList.add("odd");
if (oddItem.children.length) {
[].forEach.call(oddItem.children, function (child) {
if (child.classList.contains("box")) {
proceedEven(child);
}
})
}
}
/** Set class "even" to itself and run proceedOdd() on all children with class "box" */
function proceedEven(evenItem) {
evenItem.classList.add("even");
if (evenItem.children.length)
[].forEach.call(evenItem.children, function (child) {
if (child.classList.contains("box")) {
proceedOdd(child);
}
})
}
// set root having first even box as child
var root = document.querySelectorAll("body");
if (root.length) {
// just for case more in root
[].forEach.call(root, function (rootItem) {
if (rootItem.children.length)
[].forEach.call(rootItem.children, function (child) {
// proceed first level of evens - rest done recursively
if (child.classList.contains("box")) proceedEven(child);
});
})
}
范围是哪个。
我的代码中的任何地方都使用对象(直接或使用getter / setter)访问对象的变量,我不会将它们保存为其他地方的变量!
对不起,如果有我的问题的答案(或者如果他们是愚蠢的),但我找不到直接的问题 - 举例。当我理论上只阅读时(没有任何例子和解释,他们可能会出现什么问题)我不确定我是否认为事情正确。
谢谢你的关注。
答案 0 :(得分:1)
通常,Java可以缓存变量,直到它到达synchronized
块/方法或访问volatile
变量。在任何这些之后,它必须调用内存屏障并重新读取所有缓存变量的内容。
以上内容还包括由更高级别的函数调用的任何代码,因此在您的情况下,System.out.println()
会触发此同步操作,因此t3
将始终看到{p
的最新内容1}}。
对于MyThread.run
,它取决于“做某事”是否包含上述任何类型的同步。如果是,那么Java将不被允许缓存isRunning,如果没有它可以永远运行(仍然依赖于JIT优化等)。
关于p = Person()
的另一个注意事项 - 无论如何这都是最终的,因此第一个问题无关紧要,您将来无法随时更改p
变量,只是段落中已经回答的内容2。
答案 1 :(得分:1)
您无法找到确定的答案,因为您的代码在虚拟机中运行,并且没有定义的方式将其转换为实际代码。唯一的保证是您在JLS中找到的有关内存屏障的保证。因此,它对缓存没有任何说法,甚至你的CPU是否必须有一个。
1)可以看到p.name和t1.isRunning的错误值。
它将看到正确的值,因为它们是在线程启动时设置的。如果以后更改它们,您可能永远不会看到这种变化。
所以如果我们改变p指向t3的引用,可以看到错误的人参考,
捕获的值无法更改。这不是一个线程问题。
所以它将进入主存(RAM)并获得正确的值。
通常它不会进入主内存,无论您是否进行线程安全读取。如果CPU插槽上没有数据副本,它只会进入主存储器。在某些架构中,它甚至可以从另一个Socket复制数据而无需进入主存储器。
2)t2可以缓存t2.isRunning(它自己的isRunning变量)吗?
它不仅可以缓存它,而且可以内联它,因此它永远不会再次读取值。
我应该同步setRunning和getRunning的使用吗?
你可以这么做,但是让volatile
字段的价格要便宜一些(或更多)
所以例如,如果在t3中我有这样的代码:t2.setRunning(false)可以t2永远循环(如果t3中只有这个代码改变t2.isRunning)?
实际上,是的,它可以。如果你让它变得不稳定,那就不会。
当然,如果t2可以缓存自己的变量,我应该使用volatile或synchronize。我认为t2可以缓存它的isRunning。那么我们怎样才能理解在不同情况下哪个是可缓存的范围。
您不应该编写过多依赖于CPU缓存实现方式的代码。您应该考虑线程安全访问和内存障碍,因为这些是在JLS(Java语言规范)中定义的,因此您可以推断出这些。