I'm developing a JavaFX Terminal application which displays text coming from a Serial port at a high speed. I'm using TextArea control to display and manage the text. Upon each block of text coming from the serial port I'm using the appendText function to add the text to the terminal. I'm experiencing performance issues (high CPU usage) when updating the text to the TextArea. The following code simulates the problem, CPU usage is moving from 15%-30% which is pretty high from a simple appendText update:
public class Main extends Application {
ExecutorService executor = Executors.newFixedThreadPool(1);
@Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 0.0);
AnchorPane.setBottomAnchor(textArea, 0.0);
AnchorPane.setLeftAnchor(textArea, 0.0);
AnchorPane.setRightAnchor(textArea, 0.0);
root.getChildren().add(textArea);
textArea.setCache(true);
textArea.setCacheShape(true);
textArea.setCacheHint(CacheHint.SPEED);
Runnable runnableTask = () -> {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> {
textArea.appendText("The easiest way to create ExecutorService is to use one of the factory methods\n");
while(textArea.getText().split("\n", -1).length > 500) {
int firstLineEndIndex = textArea.getText().indexOf("\n");
textArea.replaceText(0, firstLineEndIndex+1, "");
}
});
}
};
primaryStage.setTitle("TextArea performance");
primaryStage.setScene(new Scene(root, 1000, 800));
primaryStage.show();
executor.execute(runnableTask);
}
public static void main(String[] args) {
launch(args);
}
}
Can someone explain why the CPU usage is so high? Is there a way to reduce it? Thanks!!!**
答案 0 :(得分:2)
First, you’re doing those things twenty times per second. That’s not trivial.
Second, you’re doing more than appendText. You’re calling split
on the entire text of the TextArea, again and again, for each iteration of your while
loop inside your runLater
method. Regular expressions are expensive operations. Here’s a more efficient way to limit your text to the last 500 lines:
String text = textArea.getText();
String[] lines = text.split("\n", -1);
if (lines.length > 500) {
lines = Arrays.copyOfRange(lines,
lines.length - 500, lines.length);
text = String.join("\n", lines);
textArea.setText(text);
}
Unrelated to your CPU issues, you have written a rogue thread: It ignores interrupts. An interrupt is an explicit request by other code for your thread to stop what it’s doing and exit gracefully. There is a very easy way to do this: put your while
loop inside the try
block:
try {
while (true) {
Thread.sleep(50);
Platform.runLater(() -> {
textArea.appendText("The easiest way to create ExecutorService is to use one of the factory methods\n");
// etc.
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Alternatively, since you are using an ExecutorService and not a simple Executor, you can create a Callable instead of a Runnable, and use ExecutorService.submit rather than execute, so you don’t need a try/catch at all:
Callable<Void> runnableTask = () -> {
while (true) {
Thread.sleep(50);
Platform.runLater(() -> {
// ...
});
}
};
// ...
executor.submit(runnableTask);