Java:执行两个线程,直到boolean标志为false:第二个线程的第一次运行停止了第一个线程 [英] Java: two threads executing until the boolean flag is false: the second thread's first run stops the first thread

查看:77
本文介绍了Java:执行两个线程,直到boolean标志为false:第二个线程的第一次运行停止了第一个线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有三个线程的线程程序: main lessonThread questionThread



它是这样的:




  • 课程继续完成
    时打印,变量为 true

  • 每5秒 questionThread 会问完成
    的课程吗?
    y ,它将完成设置为 false



问题是课程继续 从不打印第一次问这个问题:





另外,如图所示,有时 lessonThread 会潜入其课程继续 c,然后用户才能输入 questionThread 的问题。

 公共课Lesson {
私有布尔值已完成;
private boolean waitForAnswer;
私人扫描仪扫描仪=新的扫描仪(System.in);

私有线程lessonThread;
私有线程QuestionThread;

public static void main(String [] args){
教训= new Lesson();
lesson.lessonThread = lesson.new LessonThread();
lesson.questionThread = lesson.new QuestionThread();

lesson.lessonThread.start();
lesson.questionThread.start();
}



类LessonThread扩展了线程{
@Override
public void run(){
while(!finished &&!waitingForAnswer){
System.out.println(课程继续);
}
}
}

类QuestionThread扩展了线程{
private Instant sleepStart = Instant.now();
private boolean isAsleep = true;
@Override
public void run(){
while(!finished){
if(isAsleep&& Instant.now()。getEpochSecond()-sleepStart.getEpochSecond ()> = 5){
System.out.print(完成课程?y / n);
waitForAnswer = true;
字符串回复= Scanner.nextLine()。substring(0,1);
开关(reply.toLowerCase()){
例 y:
完成= true;
}
waitForAnswer = false;

isAsleep = true;
sleepStart = Instant.now();
}
}
}
}
}

我认为 waitingForAnswer = true 可能在这里出错,但是然后 lessonThread 有5秒钟,直到 questionThread 再次提出问题,在此期间 waitingForAnswer false



非常感谢您的帮助。



编辑:我在lessonThread的循环中找到了一笔购买,并将其更改为:

  @Override 
public void run(){
while(!finished){
if(!waitingForAnswer){
System .out.println(课程继续);
}
}
}

但是,我得到的却是相同的结果。



编辑:我可以在调试器中使用它:



解决方案

这不是您应该使用的方式与线程。这里有两个主要问题:


  1. java内存模型。

想象一个线程写入某个变量,然后在不到一秒钟的时间内,另一个线程读取了该变量。如果可以保证按您希望的方式工作,那么这意味着写操作必须一直传播到可以看到它的任何地方,然后代码才能继续..并且因为您完全不知道读取哪个字段直到某个线程真正读取它为止(java不会试图向前看并预测代码稍后将要做什么),这意味着对任何变量的最后一次写入都需要在所有变量之间进行完全传播同步可以看到它的线程...全部都是这些!现代CPU有多个内核,每个内核都有自己的缓存,如果我们应用该规则(所有更改必须在任何地方立即可见),您也可以



如果那样工作-Java会比糖蜜慢。

/ p>

所以Java 那样工作。任何线程都可以自行决定是否复制任何字段。如果线程A在某个实例的变量中写入 true,并且线程B在几秒钟后从完全相同的实例中读取了该布尔值,则java可以完全自由地操作,就好像该值是 false ...即使线程中的代码A看着它,看到的是真。稍后,任意值将同步。



那么如何使用Java中的线程?



JMM(Java内存模型)通过描述所谓的先来/后来关系来工作:仅当编写代码以清楚地表明您打算使线程A中的某个事件明显地早于其他事件发生之前,JMM(Java内存模型)才能工作线程B,那么Java将保证,一旦线程B的事件(之后出现)完成,线程A中执行的所有可见效果也将在线程B中可见。



例如,如果线程A这样做:

 已同步(someRef){
someRef.intVal1 = 1;
someRef.intVal2 = 2;
}

线程B确实:

  synchronized(someRef){
System.out.println(someRef.intVal1 + someRef.intVal2);
}

然后保证您可以在B中见证0(实际情况就是这样) B赢得了战斗,并首先到达了同步语句),或者3,如果B最后到达那里,则总是打印出来;那个同步块正在建立CBCA关系:就执行而言,获胜线程的关闭} 先于失去线程的打开,因此任何写操作都已完成线程B进入同步块时,线程A将可见该线程。



您的代码未建立任何此类关系,因此,您没有保证



您可以通过对易失性字段的写入/读取,synchronized()以及任何本身使用它们的代码来建立它们代码:java.util.concurrent包中的大多数类,启动线程以及许多其他事情在内部进行一些同步/易失性访问。


  1. 笔记本电脑的飞行问题。

现在已经不是1980年代了。您的CPU能够在任何给定时刻进行足够的计算,以汲取足够的能量来舒适地给一栋小房子供暖。您的笔记本电脑,台式机或电话不是燃烧的熔岩的原因是,CPU几乎始终完全不执行任何操作,因此不会消耗任何电流并且不会发热。实际上,一旦CPU开始运行,它将自身非常迅速地过热,并降低速度并运行得更慢。这是因为95%以上的普通PC工作负载涉及突发计算,CPU在全涡轮增压功率下可以在不到一秒钟的时间内完成计算,然后它又可以回到空转状态,而风扇和风扇散热膏和散热片会散发该功率爆发所产生的热量。这就是为什么如果您尝试做一些导致CPU长时间工作的事情(例如对视频进行编码),那么乍一看似乎要快一些,然后再放慢到稳定的水平。排空下来,您的风扇听起来像是笔记本电脑将要起飞以升上更高的轨道,并跟随Doug和Bob到达ISS-因为稳定的水平与风扇和散热器可以从CPU吸收热量一样快它不会爆炸'。速度不及寒冷时的快,但仍相当快。



这一切的结果?



您必须闲着一点CPU



类似以下内容:



while(true){ }



是所谓的忙循环:它什么也不做,永远循环,只要保持CPU忙着,就烧笔记本电脑上的一个洞,导致风扇发麻。这不是一件好事。如果您希望执行在继续之前等待某个事件,请 wait 进行操作。关键字:等等。如果只想等待5秒钟,则需要 Thread.sleep(5000)。不是忙碌的循环。如果要等到其他线程执行了一项工作,请使用核心 wait / notifyAll 系统(这些是jlObject上的方法,并与synced关键字进行交互),或者更好的是,使用java.util.concurrent中的闩锁或锁定对象,这些类很棒。如果只想确保2个线程在接触相同数据时不会发生冲突,请使用 synchronized 。所有这些功能将使CPU闲置下来。无休止地在while循环中旋转,检查if子句-这是个坏主意。



然后您将启动CBCA关系,这是任何2个条件所必需的



并且由于您使CPU负担过多的工作,因此您的'= false'写入的同步点将重新同步到另一个线程可能不会发生-通常很难观察到JMM问题(这就是使多线程编程如此棘手的原因-它很复杂,您会搞砸,很难测试错误,并且似乎永远不会亲自运行今天就遇到了这个问题,但是明天,在Winamp上的另一首歌,在另一系统上的声音会一直发生。这是观察它的好方法。


I have this threaded program that has three threads: main, lessonThread, questionThread.

It works like this:

  • Lesson continues continues to gets printed while the finished the variable is true;
  • every 5 seconds the questionThread asks Finish the lesson? and if the answer is y, it sets finished to false

The problem is that the Lesson continues never gets printed after the question gets asked the first time:

Also, as seen on the picture, sometimes lessonThread sneaks in with its Lesson continues before the user can enter the answer to the questionThread's question.

public class Lesson {
    private boolean finished;
    private boolean waitingForAnswer;
    private Scanner scanner = new Scanner(System.in);

    private Thread lessonThread;
    private Thread questionThread;

    public static void main(String[] args) {
        Lesson lesson = new Lesson();
        lesson.lessonThread = lesson.new LessonThread();
        lesson.questionThread = lesson.new QuestionThread();

        lesson.lessonThread.start();
        lesson.questionThread.start();
    }



    class LessonThread extends Thread  {
        @Override
        public void run()  {
            while (!finished && !waitingForAnswer)  {
                System.out.println("Lesson continues");
            }
        }
    }

    class QuestionThread extends Thread {
        private Instant sleepStart = Instant.now();
        private boolean isAsleep = true;
        @Override
        public void run() {
            while (!finished) {
                if (isAsleep && Instant.now().getEpochSecond() - sleepStart.getEpochSecond() >= 5) {
                    System.out.print("Finish a lesson? y/n");
                    waitingForAnswer = true;
                    String reply = scanner.nextLine().substring(0, 1);
                    switch (reply.toLowerCase()) {
                        case "y":
                            finished = true;
                    }
                    waitingForAnswer = false;

                    isAsleep = true;
                    sleepStart = Instant.now();
                }
            }
        }
    }
}

I think the waitingForAnswer = true might be at fault here, but then, the lessonThread has 5 seconds until the questionThread asks the question again, during which the waitingForAnswer is false.

Any help is greatly appreciated.

EDIT: I found a buy in the loop in the lessonThread and changed it to:

    @Override
        public void run()  {
            while (!finished)  {
                if (!waitingForAnswer)  {
                    System.out.println("Lesson continues");
                }
            }
        }

However, I get the same result.

EDIT: I can get it working when inside a debugger:

解决方案

this just isn't how you're supposed to work with threads. You have 2 major problems here:

  1. java memory model.

Imagine that one thread writes to some variable, and a fraction of a second later, another thread reads it. If that would be guaranteed to work the way you want it to, that means that write has to propagate all the way through any place that could ever see it before code can continue.. and because you have absolutely no idea which fields are read by some thread until a thread actually reads it (java is not in the business of attempting to look ahead and predict what the code will be doing later), that means every single last write to any variable needs a full propagate sync across all threads that can see it... which is all of them! Modern CPUs have multiple cores and each core has their own cache, and if we apply that rule (all changes must be visible immediately everywhere) you might as well take all that cache and chuck it in the garbage because you wouldn't be able to use it.

If it worked like that - java would be slower than molasses.

So java does not work like that. Any thread is free to make a copy of any field or not, at its discretion. If thread A writes 'true' to some instance's variable, and thread B reads that boolean from the exact same instance many seconds later, java is entirely free to act as if the value is 'false'... even if when code in thread A looks at it, it sees 'true'. At some arbitrary later point the values will sync up. It may take a long time, no guarantees are available to you.

So how do you work with threads in java?

The JMM (Java Memory Model) works by describing so called comes-before/comes-after relationships: Only if code is written to clearly indicate that you intend for some event in thread A to clearly come before some other event in thread B, then java will guarantee that any effects performed in thread A and visible there will also be visible in thread B once B's event (the one that 'came after') has finished.

For example, if thread A does:

synchronized (someRef) {
    someRef.intVal1 = 1;
    someRef.intVal2 = 2;
}

and thread B does:

synchronized(someRef) {
    System.out.println(someRef.intVal1 + someRef.intVal2);
}

then you are guaranteed to witness in B either 0 (which will be the case where B 'won' the fight and got to the synchronized statement first), or 3, which is always printed if B got there last; that synchronized block is establishing a CBCA relationship: The 'winning' thread's closing } 'comes before' the losing thread's opening one, as far as execution is concerned, therefore any writes done by thread A will be visible by thread B by the time it enters it sync block.

Your code does not establish any such relationships, therefore, you have no guarantees.

You establish them with writes/reads from volatile fields, with synchronized(), and with any code that itself uses these, which is a lot of code: Most classes in the java.util.concurrent package, starting threads, and many other things do some sync/volatile access internally.

  1. The flying laptop issue.

It's not the 1980s anymore. Your CPU is capable of doing enough calculations at any given moment to draw enough power to heat a small house comfortably. The reason your laptop or desktop or phone isn't a burning ball of lava is because the CPU is almost always doing entirely nothing whatsoever, and thus not drawing any current and heating up. In fact, once a CPU gets going, it will very very quickly overheat itself and throttle down and run slower. That's because 95%+ of common PC workloads involve a 'burst' of calculations to be done, which the CPU can do in a fraction of a second at full turboboosted power, and then it can go back to idling again whilst the fans and the cooling paste and the heat fins dissipate the heat that this burst of power caused. That's why if you try to do something that causes the CPU to be engaged for a long time, such as encoding video, it seems to go a little faster at first before it slows down to a stable level.. whilst your battery is almost visibly draining down and your fans sound like the laptop is about to take off for higher orbit and follow Doug and Bob to the ISS - because that stable level is 'as fast as the fans and heat sinks can draw the heat away from the CPU so that it doesn't explode'. Which is not as fast as when it was still colder, but still pretty fast. Especially if you have powerful fans.

The upshot of all this?

You must idle that CPU.

something like:

while (true) {}

is a so-called 'busy loop': It does nothing, looping forever, whilst keeping the CPU occupied, burning a hole into the laptop and causing the fans to go ape. This is not a good thing. If you want execution to wait for some event before continuing, then wait for it. Keyword: wait. If you just want to wait for 5 seconds, Thread.sleep(5000) is what you want. Not a busy-loop. If you want to wait until some other thread has performed a job, use the core wait/notifyAll system (these are methods on j.l.Object and interact with the synchronized keyword), or better yet, use a latch or a lock object from java.util.concurrent, those classes are fantastic. If you just want to ensure that 2 threads don't conflict while they touch the same data, use synchronized. All these features will let the CPU idle down. endlessly spinning away in a while loop, checking an if clause - that is a bad idea.

And you get CBCA relationships to boot, which is what is required for any 2 threads to communicate with each other.

And because you're overloading the CPU with work, that sync point where your '= false' writes get synced back over to the other thread probably aren't happening - normally it's relatively hard to observe JMM issues (which is what makes multithreaded programming so tricky - it is complex, you will mess up, it's hard to test for errors, and it's plausible you'll never personally run into this problem today. But tomorrow, with another song on the winamp, on another system, happens all the time). This is a fine way to observe it a lot.

这篇关于Java:执行两个线程,直到boolean标志为false:第二个线程的第一次运行停止了第一个线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆