0

I need to use two threads to append two different characters to the same JavaFX TextArea. I can get one to work, but when I add in the second thread, it breaks with some very long exception. What am I doing wrong?

I looked at this question for guidance and it got me the one thread working, but not two: Display output of two different Threads in two separate JavaFx TextArea's

package application;
    

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;



public class Main extends Application {
    
    
    @Override
    public void start(Stage primaryStage) {
        
        try {
            BorderPane root = new BorderPane();
            TextArea textArea = new TextArea();
            textArea.setWrapText(true);
            root.setCenter(textArea);
            textArea.setText("");
            
            new Thread(() -> PrintChar(textArea, 'a', 100)).start();
            new Thread(() -> PrintChar(textArea, 'b', 100)).start();
                    
            
            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
    
    private void PrintChar(TextArea textArea, char letter, int numb)
    {
        for(int i = 0; i < numb; ++i)
        {
            textArea.appendText(letter + "");
        }
    }
}
The_Redhawk
  • 214
  • 1
  • 2
  • 11
  • 3
    The UI is controlled by a single thread, and is not thread safe. To edit the UI from any other thread you need to make use of `Platform.runLater(() -> {textArea.appendText(letter + "");});`, note how the code that updates the UI from another thread is placed within the brackets. If you don't do this then you will have all sorts of issues if different threads try to interact with the same thing at the same time. By using runLater the updates get scheduled to take place one at a time as the UI thread is able to manage them while also managing user interaction. – sorifiend Apr 30 '21 at 04:03
  • 1
    @sorifiend, thanks for the response. The problem is that the lazy teacher gave this assignment from a textbook with different editions. The one the students have does not cover multithreading. She posted what she wants done and said it should be doable with the tools she covered in a 20 min video. None of what you said was covered in the video :( – The_Redhawk Apr 30 '21 at 04:09
  • I looked at that other thread, it didn't really answer my question too well though. How would synchronization work with that? – The_Redhawk Apr 30 '21 at 04:14
  • Synchronization is a whole different topic and not something you need in your scenario because the UI thread will manage this correctly. Synchronization matters when multiple threads access the same data, but using `Platform.runLater` solves this for javafx. For non UI related code it requires an entirely different approach, one way is to carefully use `volatile` variables and event queues. I strongly recommend following the official tutorial, it has some great info: https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html – sorifiend Apr 30 '21 at 04:20
  • 2
    why do you think u need concurrency? It is a school assignment most likely can be done using single thread especially since u weren’t taught concurrency yet – J Asgarov Apr 30 '21 at 04:22
  • I will for sure look at that oracle doc. Either this is a new assignment she made and has no idea what it entails, or she is not clear on the instructions. She says to demonstrate with and without synchronization. I also tried to model my code after the other example from the question you provided with bad results. – The_Redhawk Apr 30 '21 at 04:23
  • @JAsgarov The teacher wants it. Doesn't matter what is practical; the teacher is always right. It has been a very frustrating and expensive year of school to say the least, but that is a whole different topic and stack overflow doesn't have a tag for it. – The_Redhawk Apr 30 '21 at 04:24
  • What exactly does the teacher want? You don’t need two thread to append two different characters – J Asgarov Apr 30 '21 at 04:25
  • I don't suppose I can upload pic right? She says, "Rewrite listing 30.1 to display the output in a text area as shown in figure 30.30. Hint: create textarea, then use appendText because 2 threads are adding text to the text area, then they need to have it synchronized so that only one thread is appending text to the area." – The_Redhawk Apr 30 '21 at 04:30
  • Figure 30.30 is just a picture of 'a' and 'b' written to the area – The_Redhawk Apr 30 '21 at 04:30
  • java naming conventions please – kleopatra Apr 30 '21 at 08:11

1 Answers1

2

You don't need to use two threads for this task. You could simply call the PrintChar method in order:

PrintChar(textArea, 'a', 100);
PrintChar(textArea, 'b', 100);

Using this will cause the textArea output to look like this with 100 of each:

aaaaaa.....bbbbbb.....


However, if you "want" to use two threads like so:

new Thread(() -> PrintChar(textArea, 'a', 100)).start();
new Thread(() -> PrintChar(textArea, 'b', 100)).start();

Then you can schedule the UI change by making use of Platform.runLater, for example surround the code that interacts with the javafx components like so:

Platform.runLater(() -> {
    textArea.appendText(letter + "");
});

This will cause the textArea to be altered by a mix of both threads and will look something like this (you have no control over the order of execution without taking additional steps):

bbaaabbbbabbbaaaaa.....


If you want to use threads, but also use synchronization then it is no real difference from using no threads at all (at least in your scenario), however, you could do it like so:

private synchronized void PrintChar(TextArea textArea, char letter, int numb)
{
    for(int i = 0; i < numb; ++i)
    {
        //We still need to wrap the UI code, because other methods from other threads could still interact with the UI and cause issues
        Platform.runLater(() -> {
            textArea.appendText(letter + "");
        });
    }
}

And the output would look like this with 100 of each in order:

aaaaaa.....bbbbbb.....

sorifiend
  • 5,927
  • 1
  • 28
  • 45
  • 2
    just so it is clear for the OP everytime you call runLater that is another thread that is going to be executed. So this answers your question. You dont need synchronization since this is the only way to do it in JavaFX. But for the sake of the assignment feel free to throw the snippet in the method and add synchronized to it if you so please (wont hurt) – J Asgarov Apr 30 '21 at 04:34
  • Throw it into the PrintChar method for poops and giggles? – The_Redhawk Apr 30 '21 at 04:41
  • @The_Redhawk I have added a synchronized example for you, but it serves absolutely no purpose over having no threads at all, unless the intent of your teacher was to demonste to yourself how threads work. – sorifiend Apr 30 '21 at 05:03
  • 2
    @JAsgarov "everytime you call runLater that is another thread that is going to be executed". This is not true (and maybe you meant something different to what you actually state here). `Platform.runLater()` does not create or start any threads. It takes an object implementing the `Runnable` interface (in this example expressed as a lambda expression) and schedules it for execution on the already-running FX Application Thread. – James_D Apr 30 '21 at 12:24