英文:
How to call a method on one running thread but execute it on another running thread?
问题
Sure, here's the translated code part:
我是新手使用Java线程。我有一个客户端应用程序,它在两个线程上运行:`WorkerThread`和`JavaFxGuiThread`。我的问题是:我可以在“GuiThread”上调用方法(按下按钮时),但在“WorkerThread”上执行它吗?请注意,当我调用方法并执行它时,两个线程都已在执行其他操作。
以下是我的代码示例:
```java
class WorkerThread implements Runnable {
@Override
public void run() {
searchForConnections(); // <-- 应该在线程开始时运行
// 应该等待onButtonPressed(),然后才执行
// sendMessage()在WorkerThread上,而不是在GuiThread上执行
}
private String searchForConnections() {
// 不断搜索连接
return connection;
}
private void sendMessage() {
//发送消息
System.out.println("消息已发送!");
}
}
class GuiThread implements Runnable {
@Override
public void run() {
runGui(); // <-- 应该在线程开始时运行
// 等待用户按下按钮
// 然后只有:
onButtonPressed();
}
private void onButtonPressed() {
// 应该以某种方式在WorkerThread的run()方法内运行sendMessage()
// (应该在WorkerThread上运行sendMessage()而不是在GuiThread上运行)
}
}
public class Main {
public static void main(String[] args) {
new Thread(new WorkerThread()).start(); // 启动GuiThread的run()方法
new Thread(new GuiThread()).start(); // 启动WorkerThread的run()方法
}
}
我希望避免在“GuiThread”上调用sendMessage()
。
<details>
<summary>英文:</summary>
I am new to java threads. I have an client application that is running on 2 threads: `WorkerThread` and `JavaFxGuiThread`. My question is: can i call a method on `GuiThread` (on press of a button) but execute it on `WorkerThread`? Note that that at the time when i call method and execute it both threads are already doing other stuff.
Here is the sample of my code:
class WorkerThread implements Runnable {
@Override
public void run() {
searchForConnections(); // <-- should run at the start of a thread
// should wait for onButtonPressed() and only then execute
// sendMessage() on WorkerThread, not on GuiThread
}
private String searchForConnections() {
// constantly searches for connections
return connection;
}
private void sendMessage() {
//sends message
System.out.println("message sent!");
}
}
class GuiThread implements Runnable {
@Override
public void run() {
runGui(); // <-- should run at the start of a thread
// waits until user press the button
// and only then:
onButtonPressed();
}
private void onButtonPressed() {
// should somehow run sendMessage() inside
// WorkerThread run() method
// ( which should run sendMessage() on WorkerThread not GuiThread )
}
}
public class Main {
public static void main(String[] args) {
new Thread(new WorkerThread()).start(); // starts GuiThread run() method
new Thread(new GuiThread()).start(); // starts WorkerThread run() method
}
}
I want to avoid calling `sendMessage()` on `GuiThread`.
Similar answers here only show how to run methods AT THE START of a thread while i believe that my situation is different because threads are already running.
</details>
# 答案1
**得分**: 1
我相信你有一些轻微的概念误解。不要考虑如何从一个线程中调用方法,然后在不同的线程上执行它。相反,考虑如何在线程之间传递对象。你想要给你的 "工作线程" 一个要发送的消息。因此,你需要找出如何将该消息的 "对象" 传递给该线程。
基本上,你需要提供一个 "同步点",在这里你可以在线程之间传递数据(甚至操作,例如 `Runnable`)。你可以通过多种方式来实现这一点。如何具体决定要这样做取决于你的 "工作线程" 应该如何工作。不管怎样,我建议研究一下 Java 并发。看一看:
* 并发教程(例如,[这个][4] 或 [这个][5],以及许多其他教程,包括完整的书籍)。
* `synchronized` 关键字以及相应的 `wait`、`notify` 和 `notifyAll` 方法(都在 `Object` 中声明)。
* [`java.util.concurrent`][1]、[`java.util.concurrent.atomic`][2] 和 [`java.util.concurrent.locks`][3] 包。
* [JavaFX 中的并发][6] 和 [`javafx.concurrent`][7] 包(都是 JavaFX 特定的;你包括了 [tag:javafx] 标签)。
并发很复杂,需要一些工作来熟练掌握它。
也就是说,如果你的 "工作线程" 打算在用户输入多个消息时发送它们,那么可能最简单的解决方案之一是使用 [`BlockingQueue`][8]。以下是一个示例(在 JavaFX 中,考虑到 [tag:javafx] 标签):
```lang-java
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application {
private ExecutorService executor;
@Override
public void start(Stage primaryStage) {
executor = Executors.newSingleThreadExecutor();
BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(10);
executor.execute(new Messenger(messageQueue));
TextField inputField = new TextField();
Button sendBtn = new Button("Send");
sendBtn.setDefaultButton(true);
sendBtn.setOnAction(e -> {
e.consume();
String message = inputField.getText();
if (message != null && !message.isEmpty()) {
System.out.printf(
"[%s] Enqueueing message: '%s'%n",
Thread.currentThread().getName(),
message
);
messageQueue.add(message);
inputField clear();
}
});
HBox root = new HBox(10, inputField, sendBtn);
root.setPadding(new Insets(10));
HBox.setHgrow(inputField, Priority.ALWAYS);
primaryStage.setScene(new Scene(root, 600, -1));
primaryStage.setTitle("JavaFX Demo");
primaryStage.show();
}
@Override
public void stop() {
executor.shutdownNow();
}
private static class Messenger implements Runnable {
private final BlockingQueue<String> queue;
Messenger(BlockingQueue<String> queue) {
this.queue = Objects.requireNonNull(queue, "queue");
}
@Override
public void run() {
try {
searchForConnections();
while (!Thread.interrupted()) {
String message = queue.take();
sendMessage(message);
}
} catch (InterruptedException ignore) {
// If the thread is interrupted, we simply want to stop
}
}
private void searchForConnections() {}
private void sendMessage(String message) {
System.out.printf(
"[%s] Sending message: '%s'%n",
Thread.currentThread().getName(),
message
);
}
}
}
请注意,这实际上是如何工作的。我们不是从一个线程中调用方法,然后在另一个线程上执行它。我们有一个后台线程(你的 "工作线程")在 BlockingQueue
上等待要发送的消息。当 JavaFX 应用程序线程(UI 线程)在队列中放置一个 String
时,后台线程会取出它并使用 String
调用 sendMessage
。
此外,如果存在一些混淆,注意我们不会创建或管理 UI 线程。这由 UI 框架处理,线程已经在本质上以处理用户生成的事件并将渲染的 UI 与场景图的状态同步运行。
如果你运行上面的示例,在文本字段中输入 "Hello, World!",然后触发 "Send" 按钮,你应该会在控制台上看到类似以下内容:
[JavaFX Application Thread] Enqueueing message: 'Hello, World!'
[pool-2-thread-1] Sending message: 'Hello, World!'
打印到控制台。
英文:
I believe you've got a slight conceptual misunderstanding. Don't think about how to call a method from one thread and have it execute on a different thread. Instead, think about how to pass objects between threads. You want to give your "worker thread" a message to send. So, you need to figure out how to give that message object to said thread.
Essentially, you need to provide a "synchronization point" where you can pass data (or even actions, e.g., as Runnable
s<sup>1</sup>) between threads. There are quite a few ways you can do this. And how exactly you decide to do this depends on how exactly this "worker thread" of yours is supposed to work. Regardless, I recommend doing some research into Java concurrency. Take a look at:
-
Concurrency tutorials (e.g., this one or this one, among many others, including full books).
-
The
synchronized
keyword and the correspondingwait
,notify
, andnotifyAll
methods (all declared inObject
). -
The
java.util.concurrent
,java.util.concurrent.atomic
, andjava.util.concurrent.locks
packages. -
Concurrency in JavaFX and the
javafx.concurrent
package (both JavaFX-specific; you included the [tag:javafx] tag).
Concurrency is hard, and it's going to take some work to become proficient with it.
That said, if your "worker thread" is meant to send multiple messages as the user enters them, then probably one of the easiest solutions is to use a BlockingQueue
. Here's an example (in JavaFX, given the [tag:javafx] tag):
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application {
private ExecutorService executor;
@Override
public void start(Stage primaryStage) {
executor = Executors.newSingleThreadExecutor();
BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(10);
executor.execute(new Messenger(messageQueue));
TextField inputField = new TextField();
Button sendBtn = new Button("Send");
sendBtn.setDefaultButton(true);
sendBtn.setOnAction(e -> {
e.consume();
String message = inputField.getText();
if (message != null && !message.isEmpty()) {
System.out.printf(
"[%s] Enqueueing message: '%s'%n",
Thread.currentThread().getName(),
message
);
messageQueue.add(message);
inputField.clear();
}
});
HBox root = new HBox(10, inputField, sendBtn);
root.setPadding(new Insets(10));
HBox.setHgrow(inputField, Priority.ALWAYS);
primaryStage.setScene(new Scene(root, 600, -1));
primaryStage.setTitle("JavaFX Demo");
primaryStage.show();
}
@Override
public void stop() {
executor.shutdownNow();
}
private static class Messenger implements Runnable {
private final BlockingQueue<String> queue;
Messenger(BlockingQueue<String> queue) {
this.queue = Objects.requireNonNull(queue, "queue");
}
@Override
public void run() {
try {
searchForConnections();
while (!Thread.interrupted()) {
String message = queue.take();
sendMessage(message);
}
} catch (InterruptedException ignore) {
// If the thread is interrupted, we simply want to stop
}
}
private void searchForConnections() {}
private void sendMessage(String message) {
System.out.printf(
"[%s] Sending message: '%s'%n",
Thread.currentThread().getName(),
message
);
}
}
}
Note how this actually works. We aren't calling a method from one thread, but having it execute on another. We have the background thread (your "worker thread") blocked on the BlockingQueue
waiting for messages to send. When the JavaFX Application Thread (the UI thread) places a String
in the queue, the background thread takes it and calls sendMessage
with the String
.
Also, in case there's some confusion here, note how we do not create nor manage the UI thread. That is handled by the UI framework, and the thread is already running in essentially a loop that processes user-generated events and syncs the rendered UI with the state of the scene graph.
If you run the above example, type Hello, World!
into the text field, and then trigger the <kbd>Send</kbd> button, you should see something like:
[JavaFX Application Thread] Enqueueing message: 'Hello, World!'
[pool-2-thread-1] Sending message: 'Hello, World!'
Printed to the console.
<sup>1. One of the main ExecutorService
implementations, ThreadPoolExecutor
, works on this principle. It maintains a BlockingQueue<Runnable>
and a pool of threads. These threads essentially loop trying to take a Runnable
from the queue and then calling its run()
method. The queue is populated by calling e.g., execute
and submit
on the ExecutorService
. And that's one way you can give a "task" to another thread. It's also how the above example executes Messenger
on a background thread.</sup>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论