英文:
Variable 'runner' is not updated inside loop
问题
public class Test1 {
public static void main(String[] args) {
SleepRunner runner = new SleepRunner();
Thread thread = new Thread(runner);
thread.start();
while (!(runner.isFlag())) {
/*try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("END");
thread.interrupt();
}
}
public class SleepRunner implements Runnable {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
int num = (int) (Math.random() * 100);
System.out.println(Thread.currentThread().getName() + " " + num);
list.add(num);
}
flag = true;
System.out.println("30 Seconds");
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
System.out.println("Interrupted in 30 seconds");
}
System.out.println("sleep runner thread end");
}
}
英文:
Like this, I have two thread. The SleepRunner thread add some random numbers to a list then change flag to true and sleep. The main thread wait SleepRunner thread until the flag in SleepRunner object change from false to true then main thread will interrupte SleepRunner thread and the program will end.
But the question is, when the while loop is no body code in main thread, the variable 'runner' is not updated inside loop in other words The program is not over after SleepRunner thread change flag from false to true. So I tried to use debug tools in idea, but the program ended smoothly. And If I write some code, like System.out.println() or Thread.sleep(1) in while loop body at main thread, the program ended successfully too. it's too incredible! Does anyone know why this happens? Thanks.
public class Test1 {
public static void main(String[] args) {
SleepRunner runner = new SleepRunner();
Thread thread = new Thread(runner);
thread.start();
while(!(runner.isFlag())){
/*try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("END");
thread.interrupt();
}
}
public class SleepRunner implements Runnable {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep((long) (Math.random() * 200));
}
catch (InterruptedException e) {
System.out.println("Interrupted");
}
int num = (int) (Math.random() * 100);
System.out.println(Thread.currentThread().getName() + " " + num);
list.add(num);
}
flag = true;
System.out.println("30 Seconds");
try {
Thread.sleep(30000);
}
catch (InterruptedException e) {
System.out.println("Interrupted in 30 seconds");
}
System.out.println("sleep runner thread end");
}
}
答案1
得分: 2
你违反了Java内存模型。
以下是JMM的工作原理*:
每个线程在读取或更新任何字段(来自任何对象)时,会掷一枚硬币。硬币正面时,它会创建一个副本并从副本中更新/读取。硬币反面时,不会创建副本。你的任务是确保你的代码在硬币投掷的结果如何时都能正确运行,而且你不能在单元测试中强制进行硬币投掷。这枚硬币不一定是“公平”的。硬币的行为取决于音乐播放器中的音乐、幼儿的心情和月亮的相位。(换句话说,任何更新/读取都可能是对本地缓存副本的操作,也可能不是,取决于Java的实现)。
你可以放心地得出唯一正确的做法是确保线程永远不会投掷那枚硬币。
实现这一点的方法是建立所谓的“先于”关系。建立这些关系主要通过使用同步原语或调用使用同步原语的方法来完成。例如,如果我这样做:
线程X:
synchronized(x) {
x.foo();
System.out.println(shared.y);
shared.y = 10;
}
线程Y:
synchronized(x) {
x.foo();
System.out.println(shared.y);
shared.y = 20;
}
那么你就建立了一个关系:代码块A在代码块B之前运行,反之亦然,但至少你已经建立了它们必须按顺序运行的关系。
因此,这将保证打印出0 10
或0 20
。如果没有同步块,它还可以合法地打印出0 0
。这3种结果都是可以接受的结果(Java语言规范表示可以,如果你认为这没有意义的话,任何提交的错误报告都将被视为“按预期工作”)。
volatile
也可以使用,但volatile
的功能相当有限。
通常,由于这个问题__无法进行充分的测试__,在Java中只有三种正确处理线程的方法:
- 在整体上:使用Web服务器或其他应用程序框架来处理多线程。你不编写
psv main()
方法,框架会处理,你编写的只是“处理程序”。你的处理程序根本不会触碰任何共享数据。处理程序要么不共享数据,要么通过设计正确的总线(例如以可序列化事务隔离模式运行的数据库、RabbitMQ或其他消息总线)共享数据。 - 在小范围内:使用fork/join来并行执行巨大的任务。当然,该任务的处理程序不能使用任何共享数据。
- 阅读《Java并发编程实践》(这本书),优先使用
java.util.concurrent
包中的类,并且通常要成为这方面工作方式的专家,因为以其他方式进行线程处理很可能会导致你编写的错误,而这些错误可能不会被你的测试捕获,但要么会在生产时崩溃,要么会导致没有实际的多线程(例如,如果你过于热衷于对所有内容进行同步,除了一个核心外,其他所有核心都会闲着,你的代码实际上比单线程执行要慢得多)。
*)完整的解释需要一本书的篇幅。我只是为你提供了过于简化的要点,因为这仅仅是一个SO答案。
英文:
You've violated the java memory model.
Here's how the JMM works*:
Each thread, whenever any field (from any object) is read or updated, flips a coin. On heads, it will make a copy and update/read from that. On tails, it won't. Your job is to ensure your code functions correctly regardless of how the coin lands, and you can't force the coinflip in a unit test. The coin need not be 'fair'. The coin's behaviour depends on the music playing in your music player, the whims of a toddler, and the phase of the moon. (In other words, any update/read may be done to a local cache copy, or not, up to the java implementation).
You may safely conclude that the only way to do it correctly, is to ensure the thread never flips that coin.
The way to accomplish that is to establish so-called 'comes before' relationships. Establishing them is done primarily by using synchronization primitives, or by calling methods that use synchronization primitives. For example, if I do this:
thread X:
synchronized(x) {
x.foo();
System.out.println(shared.y);
shared.y = 10;
}
thread Y:
synchronized(x) {
x.foo();
System.out.println(shared.y);
shared.y = 20;
}
then you've established a relationship: code block A comes before code block B, or vice versa, but you've at least established that they must run in order.
As a consequence, this will print either 0 10
or 0 20
, guaranteed. Without the synchronized block, it can legally print 0 0
as well. All 3 results would be an acceptable result (the java lang spec says it's okay, and any bugs filed that you think this makes no sense would be disregarded as 'working as intended').
volatile
can also be used, but volatile is quite limited.
Generally, because this cannot be adequately tested, there are only 3 ways to do threading properly in java:
- 'in the large': Use a webserver or other app framework that takes care of the multithreading. You don't write the
psv main()
method, that framework does, and all you write are 'handlers'. None of your handlers touch any shared data at all. The handlers either don't share data, or share it via a bus designed to do it right, such as a DB in serializable transaction isolation mode, or rabbitmq or some other message bus. - 'in the small': Use fork/join to parallellize a giant task. The handler for the task cannot, of course, use any shared data.
- read Concurrency in Practice (the book), prefer using the classes in the java.util.concurrent package, and in general be a guru about how this stuff works, because doing threading any other way is likely to result in you programming bugs which your tests probably won't catch, but will either blow up at production time, or will result in no actual multithreading (e.g. if you overzealously synchronize everything, you end up having all cores except one core just waiting around, and your code will actually run way slower than if it was just single threaded).
*) The full explanation is about a book's worth. I'm just giving you oversimplified highlights, as this is merely an SO answer.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论