我有一个Thread-X,它每秒读取一个非易失性变量,这样做没有任何同步方法。
现在我想知道是否有某种方法可以修改Thread-Y上的非易失性变量,以便Thread-Y的写入(最终)在Thread-X上可见?
public class Test {
private static boolean double = 1; // this variable is
// strictly not volatile
public static void main(String args[]) {
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
while (true) {
System.out.println(variable);
try {
java.lang.Thread.currentThread().sleep(1000);
} catch (java.lang.InterruptedException e) {
}
}
}
}).start(); // line 17
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
// the task is to change variable to "2" such the write
// is (eventually) registered by the other threads
// allowed to use any synchronization/locking techniques
// required, but line 1 to line 17 must not be changed
}
}).start();
}
}
是否可以修改非易失性变量,以便在没有任何同步技术(原始读取)的情况下读取它的另一个线程最终能够“看到”更新?
背景
我需要从大量线程中读取变量,无限次。
根据我的理解(如果我错了,请纠正我),在大多数cpus(例如x86)中,对volatile变量的读取“几乎完全免费”但不是“完全免费”。
现在因为我从无限个线程中读取了无限次数,所以我希望变量非易失性 。然而,一旦在蓝色的月亮中,变量需要更新。在我的用例中,更新该变量需要花费多少并不重要,但该更新最终必须由读取器线程读取。
解决方案:
基于Tomasz's comment,我已经构建了这个解决方案,我想知道解决方案-1是否存在缺陷或者它是否可靠?
public class Solution1 {
private static double variable = 1; // this variable is
// strictly not volatile
public static void main(String args[]) {
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
while (true) {
System.out.println(variable);
try {
java.lang.Thread.currentThread().sleep(1000);
} catch (java.lang.InterruptedException e) {
}
}
}
}).start(); // line 17
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
variable = 2;
// writer-thread now terminates,
// is it guaranteed that when it
// "terminates successfully", variable
// is updated on the reader-thread ?
}
}).start();
}
}
基于Joonas's comment,我已经构建了这个解决方案,我想知道解决方案-2存在缺陷还是很稳固?
public class Solution2 {
private static double variable = 1; // this variable is
// strictly not volatile
private static volatile boolean lock = false;
public static void main(String args[]) {
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
while (true) {
System.out.println(variable);
try {
java.lang.Thread.currentThread().sleep(1000);
} catch (java.lang.InterruptedException e) {
}
}
}
}).start(); // line 17
new java.lang.Thread(new java.lang.Runnable() {
@Override
public void run() {
variable = 2;
lock = false; // does this line guarantee
// that other threads will now
// see the update to variable (piggypacking)?
// now let's assume this thread doesn't terminate
}
}).start();
}
}
答案 0 :(得分:5)
是否可以修改非易失性变量,以便在没有任何同步技术(原始读取)的情况下读取它的另一个线程能够最终“看到”更新?
否。必须使用某种同步技术,否则允许(JIT)编译器将您的行优化为System.out.println(false);
(如果false
是第一次看到的值通过那个线程)。也就是说,它可以优化读取变量。
我不知道它实际上有多大可能做到这一点,但根据Java内存模型可以,所以你的选择是:
volatile
。这可能是您最简单,最轻的选择。AtomicBoolean
。关于它与易变here和here的讨论。synchronized
阻止。在这种情况下矫枉过正。java.util.concurrent.locks.Lock
。比synchronized
更多的功能。Thread.join()
。在这种情况下没用(读取线程会等待编写器终止)。只需使用volatile
并让JVM担心有效地实施它。 It's not expensive.
答案 1 :(得分:3)
Doug Lea从Synchronization and the Java Memory Model引用Concurrent Programming in Java:
仅在以下条件下,对一个线程所做的字段的更改才能保证对其他线程可见:
写作线程释放同步锁,读取线程随后获取相同的同步锁。
如果某个字段被声明为 volatile ,则在编写器线程执行任何进一步的内存操作之前,写入该文件的任何值都会被写入线程刷新并使其可见(即,用于此目的)在手边它立即被冲洗)。读取器线程必须在每次访问时重新加载volatile字段的值。
第一次线程访问对象的字段时,它会看到字段的初始值或自某个其他线程写入的值。
当线程终止时,所有写入的变量都会刷新到主内存。例如,如果一个线程使用Thread.join在另一个线程的终止上同步,那么可以保证看到该线程产生的效果(参见§4.3.2)。
最后两个选项不适用于您的情况,因此您需要volatile
或synchronized
,抱歉。请注意,AtomicInteger.get()
只返回volatile
值,因此除了额外的图层外什么都没有。
答案 2 :(得分:3)
我正在努力获得“完全免费”的读取,并且我准备做一个非常昂贵的写作来换取“完全免费”的读取。对于这个问题,没有比将其声明为易失性更好的解决方案吗?
没有什么是完全免费的。读取未被改变的易失性变量可以是亚纳秒,这可能足够快。如果您在写入后阅读它可能需要5纳秒。无论你的“免费”读取是什么,分支可能需要5-10纳秒,如果你做的事情就像花时间一样简单,例如使用System.nanoTime(),这可能需要20 - 180纳秒,具体取决于您的操作系统。您的阅读成本应该在您关注的列表中非常低。
例如,您应该担心代码是否已预热,以便编译而不是解释。这可以产生更大的差异(见下文)
volatile
,但我不相信你有其中一种。
最常见的情况是@Joonas Pulakka提到JIT以你不想要的方式优化字段,即它停止读取值并使其成为局部变量,因为它不易变。
这种优化可以在循环快速连续迭代10,000次后发生。在您的情况下,它不是快速连续,但超过2.7小时,因此JIT可能永远不会优化代码。