提问人:Epitaph 提问时间:12/24/2008 更新时间:8/16/2017 访问量:29269
Java while 循环和 Threads![复制]
Java while loop and Threads! [duplicate]
问:
我有一个程序,可以不断轮询数据库以查找某个字段的值变化。它在后台运行,目前使用 while(true) 和 sleep() 方法来设置间隔。我想知道这是否是一个好的做法?而且,还有什么更有效的方法来实现这一点?该程序旨在始终运行。
因此,停止程序的唯一方法是在进程 ID 上发出 kill。该程序可能处于 JDBC 调用的中间。我怎样才能更优雅地终止它?我知道最好的选择是通过使用线程会定期检查的标志来设计某种退出策略。但是,我想不出更改此标志值的方法/条件。有什么想法吗?
答:
这真的是一个太大的问题,无法用这种格式完全回答。帮自己一个忙,去购买 Java Concurrency in Practice。在 Java 5+ 平台上,没有比这更好的并发资源了。有一整章专门讨论这个主题。
关于在JDBC调用期间终止进程的问题,这应该没问题。我相信中断JDBC调用存在问题(因为你不能?),但这是一个不同的问题。
评论
为 SIGTERM 设置一个信号处理程序,该处理程序设置一个标志,告诉您的循环下次退出。
评论
我想知道这是否是一个好的做法?
不。这不好。有时,这就是你所拥有的一切,但这并不好。
而且,还有什么更有效的方法来实现这一点?
首先,事物是如何进入数据库的?
最好的更改是修复插入/更新数据库的程序,以发出转到数据库和程序的请求。JMS 主题对这种事情有好处。
下一个最佳更改是向数据库添加一个触发器,以将每个插入/更新事件排入队列。队列可以提供 JMS 主题(或队列)以供程序处理。
后备计划是轮询循环。
但是,您的轮询循环不应该轻而易举地起作用。它应该将消息放入队列中,以便其他 JDBC 进程进行处理。终止请求是可以放入 JMS 队列的另一条消息。当您的程序收到终止消息时,它绝对必须与之前的 JDBC 请求一起完成,并且可以正常停止。
在执行这些操作之前,请查看 ESB 解决方案。Sun 的 JCAPS 或 TIBCO 已经有了这个。像 Mulesource 或 Jitterbit 这样的开源 ESB 可能已经构建并测试了此功能。
如果这是您的应用程序,并且您可以对其进行修改,则可以:
- 让它读取一个文件
- 读取标志的值。
- 当您想杀死它时,您只需修改文件,应用程序就会优雅地退出。
不需要那么努力。
评论
请注意,计时器(或类似)会更好,因为您至少可以重用它,并让它处理睡眠、调度、异常处理等所有细节......
您的应用可能死机的原因有很多。不要只关注一个。
如果从理论上讲,你的JDBC工作有可能使事情处于半正确状态,那么你就有一个错误,你应该修复。所有数据库工作都应位于事务中。它应该去或不去。
关于“该程序可能处于 JDBC 调用中间”的问题。我怎样才能更优雅地终止它?“- 请参阅如何中止正在运行的 jdbc 事务?
请注意,使用 sleep() 的轮询很少是正确的解决方案 - 实施不当,它最终可能会占用 CPU 资源(JVM 线程调度器最终会花费过多的时间休眠和唤醒线程)。
这就是 Java。将处理移至第二个线程。现在你可以了
- 循环从 stdin 读取。如果有人键入“QUIT”,请将 while 标志设置为 false 并退出。
- 使用 STOP 按钮创建 AWT 或 Swing 框架。
- 假装您是 Unix 守护程序并创建一个服务器套接字。等待有人打开套接字并发送“QUIT”。(这还有一个额外的好处,您可以将睡眠更改为超时选择。
这肯定有数百种变体。
我在当前公司的实用程序库中创建了一个 Service 类,用于解决以下类型的问题:
public class Service implements Runnable {
private boolean shouldStop = false;
public synchronized stop() {
shouldStop = true;
notify();
}
private synchronized shouldStop() {
return shouldStop;
}
public void run() {
setUp();
while (!shouldStop()) {
doStuff();
sleep(60 * 1000);
}
}
private synchronized sleep(long delay) {
try {
wait(delay);
} catch (InterruptedException ie1) {
/* ignore. */
}
}
}
当然,这还远未完成,但您应该了解要点。这将使您能够在希望程序停止时简单地调用该方法,并且它将干净地退出。stop()
评论
正如其他人所说,您必须轮询的事实可能表明您的系统设计存在更深层次的问题......但有时就是这样,所以......
如果你想更优雅地处理“杀死”这个过程,你可以安装一个关闭钩子,当你点击+时会调用它:CtrlC
volatile boolean stop = false;
Runtime.getRuntime().addShutdownHook(new Thread("shutdown thread") {
public void run() {
stop = true;
}
});
然后定期检查停止变量。
一个更优雅的解决方案是等待事件:
boolean stop = false;
final Object event = new Object();
Runtime.getRuntime().addShutdownHook(new Thread("shutdown thread") {
public void run() {
synchronized(event) {
stop = true;
event.notifyAll();
}
}
});
// ... and in your polling loop ...
synchronized(event) {
while(!stop) {
// ... do JDBC access ...
try {
// Wait 30 seconds, but break out as soon as the event is fired.
event.wait(30000);
}
catch(InterruptedException e) {
// Log a message and exit. Never ignore interrupted exception.
break;
}
}
}
或者类似的东西。
您可以将该字段设置为复合值,该值包含(概念上)进程 ID 和时间戳。[更好的是,使用两个或更多字段。在进程中启动一个拥有该字段访问权限的线程,并让它循环、休眠并更新时间戳。然后,等待拥有对字段的访问权限的轮询进程可以观察到时间戳在某个时间 T 内未更新(这远大于更新循环的睡眠间隔时间),并假设先前拥有的进程已死亡。
但这仍然容易失败。
在其他语言中,我总是尝试使用 flock() 调用来同步文件。不确定 Java 等效项是什么。如果可能的话,获得真正的并发性。
我很惊讶没有人提到在 Java 中实现的中断机制。它应该是停止线程问题的解决方案。所有其他解决方案都至少有一个缺陷,这就是为什么需要在 Java 并发库中实现此机制的原因。
您可以通过向线程发送 interrupt() 消息来停止线程,但线程还有其他方式被中断。发生这种情况时,将引发 InterruptedException。这就是为什么你必须在调用 sleep() 时处理它的原因。您可以在此处进行清理并优雅地结束,例如关闭数据库连接。
我认为您应该使用 timertask 轮询它。
我的电脑在 10 秒内运行了 1075566 次循环。 这在一秒钟内107557次。
真正需要多久进行一次轮询?TimerTask 在 1 秒内以最快的速度运行 1000 次。你给它一个 int(毫秒)的参数作为参数。如果您对此感到满意 - 这意味着您在执行该任务时将 CPU 的压力降低 108 倍。
如果您对每秒轮询一次感到满意,那就是 (108 * 1000)。压力降低 108 000 倍。这也意味着您可以使用与一个 while 循环相同的 cpu 压力来检查 108 000 个值 - 因为您没有分配 cpu 来经常检查。请记住,CPU 有一个时钟周期。我的是 3 600 000 000 赫兹(每秒周期数)。
如果您的目标是为用户更新它 - 您可以在用户每次登录时运行检查(或手动让他请求更新) - 这实际上不会给 CPU 带来任何压力。
您还可以使用来降低轮询线程的压力(因为它不会经常轮询)。thread.sleep(miliseconds);
Java9 对此有另一个“潜在”的答案:Thread.onSpinWait():
指示调用方暂时无法继续,直到其他活动发生一个或多个操作。通过在旋转-等待循环构造的每次迭代中调用此方法,调用线程向运行时指示它正在忙等待。运行时可能会采取措施来提高调用旋转-等待循环构造的性能。
有关详细信息,请参阅 JEP 285。
评论