简体   繁体   English

线程永远不会停止在Java中

[英]Thread never stops in Java

I am reading Effective Java and in Chapter 10: Concurrency; 我正在阅读Effective Java第10章:并发; Item 66: Synchronize access to shared mutable data , there is some code like this: 第66项:同步对共享可变数据的访问 ,有一些代码如下:

public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
    // TODO Auto-generated method stub
    System.out.println(stopRequested);
    Thread backgroundThread = new Thread(new Runnable(){

        @Override
        public void run() {
            // TODO Auto-generated method stub
            int i = 0;
            while (!stopRequested){
                i++;
            }

            System.out.println("done");
        }

    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    stopRequested = true;
}

}

First, I think the thread should run one second and then stop, since the stopRequested is set to true afterwards. 首先,我认为线程应运行一秒然后停止,因为之后stopRequested设置为true However, the program never stops. 但是,该计划永远不会停止。 It will never print done . 它永远不会打印done The author said 作者说

while (!stopRequested)
    i++;

will be transformed into this: 将变成这样:

if (!stopRequested)
     while(true)
         i++;

Could someone explain me this? 有人可以解释一下吗?

And another thing I find is that if I change the program to this: 我发现的另一件事是,如果我将程序更改为:

public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
    // TODO Auto-generated method stub
    System.out.println(stopRequested);
    Thread backgroundThread = new Thread(new Runnable(){

        @Override
        public void run() {
            // TODO Auto-generated method stub
            int i = 0;
            while (!stopRequested){
                i++;
                System.out.println(i);
            }

            System.out.println("done");
        }

    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    stopRequested = true;
}

} }

The program runs 1 second and stops as expected. 该程序运行1秒钟,并按预期停止。 What's the difference here? 这有什么区别?

I doubt the author actually said that (exactly). 我怀疑作者实际上是这么说的(确切地说)。

But the point is that 但重点是

while (!stopRequested)
    i++;

could behave like 可能表现得像

if (!stopRequested)
     while(true)
         i++;

since the Java spec allows the initial value of stopRequested to be cached in a register, or fetched from a (potentially stale) copy in the memory cache. 因为Java规范允许将stopRequested的初始值缓存在寄存器中,或者从内存缓存中的(可能过时的)副本中获取。 One thread is not guaranteed to read the results of memory writes made by another thread unless there is a formal "happens before" relationship between the write and the subsequent read. 除非在写入和后续读取之间存在正式的“之前发生”关系, 否则不保证一个线程读取由另一个线程进行的存储器写入的结果。 In this case, there is no such relationship. 在这种情况下,没有这种关系。 That means that it is not specified whether the child thread will see the result of the parent thread's assignment to stopRequested . 这意味着没有指定子线程是否会看到父线程对stopRequested的赋值stopRequested

As the author of that book would have explained, the solutions include: 正如该书的作者所解释的那样,解决方案包括:

  • declaring stopRequested as volatile , 声明stopRequestedvolatile
  • making sure that the code that reads and writes stopRequested does so within a synchronized blocks or methods that synchronizes on the same object, 确保读取和写入stopRequested的代码在synchronized块或在同一对象上synchronized方法中执行此操作,
  • uses Lock objects rather than synchronized , or 使用Lock对象而不是synchronized ,或
  • some other concurrency mechanism that satisfies the "happens before" requirement. 一些其他并发机制,满足“之前发生”的要求。

Then you ask why your test seemed to work. 然后你问为什么你的测试似乎有效。

That's is explained by the fact that while the child is not guaranteed to see the effect of the parent's assignment, it is also not guaranteed to NOT see it ... either. 这是因为虽然孩子不能保证看到父母的任务的影响,但也不能保证不会看到它......也是如此。

Or to put it another way. 或者用另一种方式。 The Java spec does not say which of the two possibilities will happen. Java规范没有说明这两种可能性中的哪一种会发生。

Now for a specific program, compiled by a specific compiler, run by a specific version of the JVM on specific hardware, you could find that the program behaved one way (or the other way) consistently. 现在,对于由特定编译器编译的特定程序,由特定硬件上的特定版本的JVM运行,您可以发现程序以一种方式(或另一种方式)一致地运行。 Maybe 99.9% of the time. 也许99.9%的时间。 Maybe even 100% of the time. 也许甚至100%的时间。 But the same program compiled and run in a different context could behave differently. 但是在不同的上下文中编译和运行的相同程序可能表现不同。 The JLS says so. JLS这样说。


Another explanation for why the two almost identical versions of the program behave differently is that the System.out PrintWriter object is doing some internal synchronization when println is called. 另外解释为什么程序的两个几乎完全相同的版本表现不同的是System.out PrintWriter对象在调用println时进行一些内部同步。 That could be giving you a serendipitous "happens before". 这可能会给你一个偶然的“发生之前”。

I think you are making Joshua Bloch (the author of that great book) say something that he did not say :-). 我想你正在制作Joshua Bloch(这本伟大着作的作者)说出一些他没说的内容:-)。 To be precise, the book says the following (only emphasize mine): 确切地说,这本书说明了以下内容(仅强调我的):

In the absence of synchronization , it's quite acceptable for the virtual machine to transform this code: 在没有同步的情况下,虚拟机转换此代码是完全可以接受的:

while (!done)
  i++;

into this code: 进入这段代码:

if (!done)
  while (true)
    i++;

To understand what he means (it's rather tough to explain it in a way better than he has himself done in pages 261-264, but I will try. Sorry Josh!) you should first try to run this program verbatim and see what is happening. 要理解他的意思(以比他在第261-264页中所做的更好的方式解释它是相当困难的,但我会尝试。抱歉Josh!)你应该首先尝试逐字运行这个程序,看看发生了什么。 With multithreading, anything is possible, but here's what I did: 使用多线程,一切皆有可能,但这就是我所做的:

  1. Coded up StopThread as is. 编码StopThread原样。
  2. Ran it on my Linux computer with JRE 1.8.0_72. 用JRE 1.8.0_72在我的Linux计算机上运行它。
  3. It simply hung! 它只是挂了! So, the behavior has not changed from what he described. 所以,这种行为并没有改变他所描述的。
  4. Then I took the 'thread dump' to see what is happening. 然后我采取'线程转储'来看看发生了什么。 You can simply send a kill -3 signal to the running JVM pid to see what the threads are doing. 您只需向运行的JVM pid发送kill -3信号即可查看线程正在执行的操作。 Here's what I observed (relevant portion of the thread dump): 这是我观察到的(线程转储的相关部分):
 "DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007fd678009800 nid=0x1b35 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-0" #9 prio=5 os_prio=0 tid=0x00007fd6780f6800 nid=0x1b43 runnable [0x00007fd64b5be000] java.lang.Thread.State: RUNNABLE at StopThread$1.run(StopThread.java:14) at java.lang.Thread.run(Thread.java:745) "Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007fd6780c9000 nid=0x1b41 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE 

As you can see, the background thread that we started is alive, doing something . 正如你所看到的,我们就开始在后台线程是活的,做一些事情 I looked at my computer's diagnosis tool named top and here is what it shows: 我看了一下名为top计算机诊断工具,这里显示的是:

顶部cmd输出 .

Can you see that one of my CPU's (it's a quad core computer) is fully busy (100%!) doing something , moreover, it is the java process that is doing something . 你能看到我的一个CPU(它是一个四核计算机)完全忙碌(100%!) 做某事 ,而且,这是java进程正在做的事情 Isn't it puzzling? 难道不是很令人费解吗? Well it is somewhat puzzling. 好吧有点令人费解。 When a CPU is busily doing something you don't understand, one very likely cause is that it is tirelessly checking contents of some memory location. 当CPU忙于做一些你不理解的事情时,一个很可能的原因是它正在不知疲倦地检查一些内存位置的内容。 In this case, if you try to connect the dots, it's the stopRequested variable whose value (as we can expect) it is constantly reading. 在这种情况下,如果你尝试连接点,它就是stopRequested变量,其值(我们可以预期)会不断读取。 So, effectively, the CPU is just reading the value of the boolean, finding it false, all the time and it goes back to checking if it has changed! 所以,实际上,CPU只是读取布尔值,一直发现它是假的,它会回到检查它是否已经改变! Again, it finds it has not (it is still hanging on my machine as I write this :-)). 再一次,它发现它没有(它仍然挂在我的机器上,因为我写这个:-))。

You'll say ... Didn't the main thread (which, by the way, has long gone, since it does not appear in the thread dump) stopRequested = true ? 你会说......不是main thread (顺便说一下,它已经很久了,因为它没有出现在线程转储中) stopRequested = true

Yes, it did! 是的,它确实!

Naturally, you would suspect then, why doesn't the Thread-0 see it? 当然,你会怀疑,为什么Thread-0 看到它?

And therein lies the clue. 这就是线索。 In the presence of a data race, the value that a thread writes is not visible to another thread that reads it. 在存在数据争用时,线程写入的值对于读取它的另一个线程是不可见的

Now we look at the declaration of that data, that variable that shows this peculiar behavior: 现在我们看一下该数据的声明,该变量显示了这种特殊的行为:

private static boolean stopRequested;

is what it is! 它是什么! This particular declaration is rather underspecified as far as its treatment by various involved parties (the compiler, the just in time compiler and its optimizations ...) is concerned. 这种特殊的声明而得以确认,只要参与方的处理(编译器,即时编译器和优化...)有关。 In the case of such underspecification, anything may happen. 在这种不明确的情况下,任何事情都可能发生。 In particular, the value that the main thread (thought it) wrote may never be actually written into the main memory for Thread-0 to read, making it go into an infinite loop. 特别是,主线程(认为它)所写的值可能永远不会被写入主内存中以供Thread-0读取,从而使其进入无限循环。

Thus, this is a visibility issue. 因此,这是一个可见性问题。 Without enough synchronization, it is not guaranteed that the value written by a thread will be seen by another thread. 如果没有足够的同步,则无法保证线程写入的值将被另一个线程看到

Does that explain? 这解释了吗? For more details, we all need to have a better understanding of modern hardware. 有关更多详细信息,我们都需要更好地了解现代硬件。 An excellent resource for this is The Art of Multiprocessor Programming by Herlihy and Shavit. Herlihy和Shavit 的多处理器编程艺术是一个很好的资源。 This book makes a software engineer understand the intricacies of the hardware and also explains why the multithreading is so hard. 本书使软件工程师了解硬件的复杂性,并解释了为什么多线程如此困难。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM