java - Java:两个线程执行,直到布尔标志为假:第二个线程的第一次运行停止第一个线程
问题描述
我有这个线程程序,它有三个线程:main、courseThread、questionThread。
它是这样工作的:
Lesson continues
finished
变量为true
;时继续打印- questionThread每 5 秒询问一次
Finish the lesson?
,如果答案是y
,则设置finished
为false
问题是在第一次询问问题后Lesson continues
永远不会打印:
此外,如图所示,有时在用户输入questionThread 问题的答案之前,课程线程会潜入其中。Lesson continues
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();
}
}
}
}
}
我认为这waitingForAnswer = true
可能是错误的,但是,在 questionThread 再次提出问题之前,courseThread有 5 秒的时间,在此期间is 。waitingForAnswer
false
任何帮助是极大的赞赏。
编辑:我在课程线程中的循环中找到了一个购买并将其更改为:
@Override
public void run() {
while (!finished) {
if (!waitingForAnswer) {
System.out.println("Lesson continues");
}
}
}
但是,我得到相同的结果。
编辑:在调试器中我可以让它工作:
解决方案
这不是您应该使用线程的方式。您在这里有两个主要问题:
- java内存模型。
想象一下,一个线程写入某个变量,几分之一秒后,另一个线程读取它。如果可以保证以您希望的方式工作,那意味着写入必须一直传播到任何可以看到它的地方,然后代码才能继续。因为您完全不知道哪些字段被读取某个线程,直到一个线程实际读取它(Java 不打算向前看并预测代码稍后会做什么),这意味着每次对任何变量的最后写入都需要在所有线程之间进行完全传播同步看看……这就是全部!现代 CPU 有多个核心,每个核心都有自己的缓存,如果我们应用该规则(所有更改必须立即在任何地方可见),您不妨将所有缓存扔进垃圾箱,因为您无法做到用它。
如果它像那样工作 - java 会比糖蜜慢。
所以java不能那样工作。任何线程都可以自由决定是否复制任何字段。如果线程 A 将 'true' 写入某个实例的变量,而线程 B 在几秒钟后从完全相同的实例中读取该布尔值,则 java 完全可以自由地表现得好像该值为 'false'......即使当线程中的代码A 看着它,它看到了“真实”。在稍后的某个任意时间点,这些值将同步。这可能需要很长时间,您无法获得任何保证。
那么如何在 java 中使用线程呢?
JMM(Java 内存模型)通过描述所谓的“先于/后”关系来工作:只有在编写代码时清楚地表明您打算让线程 A 中的某个事件明显地出现在线程 B 中的某个其他事件之前,然后java 将保证在线程 A 中执行且可见的任何效果在线程 B 中也将在线程 B 中可见,一旦 B 的事件(“紧随其后”的事件)完成。
例如,如果线程 A 这样做:
synchronized (someRef) {
someRef.intVal1 = 1;
someRef.intVal2 = 2;
}
和线程 B 做:
synchronized(someRef) {
System.out.println(someRef.intVal1 + someRef.intVal2);
}
那么你保证在 B 中见证 0(这将是 B“赢得”战斗并首先到达同步语句的情况)或 3,如果 B 最后到达那里,则始终打印;该同步块正在建立 CBCA 关系:}
就执行而言,“获胜”线程的关闭“出现在”失败线程的打开线程之前,因此线程 A 所做的任何写入都将在线程 B 进入时可见它同步块。
您的代码没有建立任何此类关系,因此,您没有任何保证。
您可以通过 volatile 字段的写入/读取、synchronized() 以及本身使用这些的任何代码来建立它们,这是很多代码:java.util.concurrent 包中的大多数类、启动线程和许多其他东西在内部进行一些同步/易失性访问。
- 会飞的笔记本电脑问题。
现在已经不是 1980 年代了。您的 CPU 能够在任何给定时刻进行足够的计算,以获取足够的电力来舒适地加热小房子。您的笔记本电脑或台式机或手机不是燃烧的熔岩球的原因是因为 CPU 几乎总是完全不做任何事情,因此不会吸收任何电流和升温。事实上,一旦 CPU 运行起来,它会很快地过热并降低运行速度并降低运行速度。这是因为 95% 以上的常见 PC 工作负载涉及要完成的“突发”计算,CPU 可以在完全涡轮增压的情况下在几分之一秒内完成,然后它可以在风扇和冷却膏和散热片可以消散这种功率爆发引起的热量。那' 这就是为什么如果你尝试做一些导致 CPU 长时间使用的事情,比如编码视频,它一开始似乎会快一点,然后才会减慢到稳定的水平.. 而你的电池几乎是可见的耗尽电量,您的风扇听起来就像笔记本电脑即将起飞进入更高的轨道并跟随道格和鲍勃前往国际空间站 - 因为稳定的水平“与风扇和散热器可以将热量从 CPU 中带走一样快,因此它不会爆炸'。这不像天气更冷的时候那么快,但仍然相当快。特别是如果你有强大的粉丝。当您的电池几乎明显耗尽并且您的风扇听起来像是笔记本电脑即将起飞进入更高的轨道并跟随道格和鲍勃前往国际空间站时 - 因为稳定的水平是“风扇和散热器可以吸收热量的速度”远离 CPU,以免它爆炸”。这不像天气更冷的时候那么快,但仍然相当快。特别是如果你有强大的粉丝。当您的电池几乎明显耗尽并且您的风扇听起来像是笔记本电脑即将起飞进入更高的轨道并跟随道格和鲍勃前往国际空间站时 - 因为稳定的水平是“风扇和散热器可以吸收热量的速度”远离 CPU,以免它爆炸”。这不像天气更冷的时候那么快,但仍然相当快。特别是如果你有强大的粉丝。
这一切的结果?
你必须让那个 CPU 空闲。
就像是:
while (true) {}
是一个所谓的“忙循环”:它什么都不做,永远循环,同时保持 CPU 被占用,在笔记本电脑上烧一个洞并导致风扇变成猿。这不是一件好事。如果您希望执行在继续之前等待某个事件,请等待它。关键词:等等。如果你只想等待5秒,Thread.sleep(5000)
就是你想要的。不是忙循环。如果您想等到某个其他线程执行了一项工作,请使用核心wait/notifyAll
系统(这些是 jlObject 上的方法并与 synchronized 关键字交互),或者更好的是,使用 java.util.concurrent 中的锁存器或锁对象,这些课程太棒了。如果您只想确保 2 个线程在接触相同数据时不会发生冲突,请使用synchronized
. 所有这些功能都会让 CPU 空闲下来。在 while 循环中无休止地旋转,检查 if 子句 - 这是一个坏主意。
并且您可以启动 CBCA 关系,这是任何 2 个线程相互通信所必需的。
而且由于您的工作使 CPU 过载,因此您的“= false”写入同步回另一个线程的同步点可能不会发生 - 通常很难观察到 JMM 问题(这就是多线程编程的原因太棘手了——它很复杂,你会搞砸,很难测试错误,而且你今天可能永远不会亲自遇到这个问题。但是明天,在另一个系统上,winamp 上的另一首歌,所有的事情都会发生时间)。这是观察它的好方法。
推荐阅读
- android - 启用 proguard 时出现错误
- node.js - 我如何使用 Promise.all 使其工作?
- sql - 为了避免在递归 CTE 中使用 2 个 Oracle / SQL 表进行循环引用
- vue.js - Vue Select 或 Vue 多选,如何设置默认值并使其可点击
- java - JAVA_HOME 无效(cordova)错误
- java - 为什么在通过 JSP 将 MultipartFile 图像作为 byte[] 上传到 DB 时需要将 BindingResult 作为方法参数?
- firebase - Firebase signInWithCustomToken 未过期?
- machine-learning - 错误:找到暗淡 3 的数组。预计估计器 <= 2。MLPClassifier
- ios - 无法设置 Datepicker,报错
- reactjs - 使用 reactjs 更改 URL 无限滚动