Java并发 - 什么是" cachable"范围

时间:2016-01-17 19:12:36

标签: java concurrency scope

我试图理解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)t3p.name可能会看到错误的值t1.isRunning。 - >我想No,因为我认为它可以缓存对这些对象的引用,所以如果我们更改pt3的引用,可以看到错误的人参考,但在这种情况下它将缓存正确的引用,因此它将进入主存储器(RAM)并将获得正确的值。 2)可以t2缓存t2.isRunning(自己的isRunning变量)吗?我应该同步setRunninggetRunning的使用情况吗?

例如,如果在t3中我有这样的代码:t2.setRunning(false) t2可以t3永久循环(如果只有t2.isRunning中的此代码更改t2) ? 当然,如果volatile可以缓存自己的变量,我应该使用synchronizet2。我认为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)访问对象的变量,我不会将它们保存为其他地方的变量! 对不起,如果有我的问题的答案(或者如果他们是愚蠢的),但我找不到直接的问题 - 举例。当我理论上只阅读时(没有任何例子和解释,他们可能会出现什么问题)我不确定我是否认为事情正确。 谢谢你的关注。

2 个答案:

答案 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语言规范)中定义的,因此您可以推断出这些。