Java writing to files from a separate thread

I have a Java program that writes to a file whenever a user does a certain action. To avoid blocking the main thread I thought it would be best to handle writing to that file on a separate thread. Anyways I am wondering what's the best way to go about that. At first I created a new object that implements Runnable. Then when ever I needed to write to a file I created a new instance of that object and a new thread, passed in the runnable as a parameter to the thread and started it. This works fine however I was not sure if this was the best approach and based on my research apparently creating a new thread object is very memory intensive.

            final UpdateConfigRunnable runnable = new UpdateConfigRunnable(stringData, object);
            final Thread thread = new Thread(runnable);
            thread.start();

Normally I was passing in an object (all data being queried from that object is final) and some string data into the constructor of my runnable object. One issue is I cant generate a new runnable and pass it into the same thread which means I need to make the thread once and the runnable once then maybe update stringData with a setter method before I call the start method? That implantation would look like this.

public class NormalClass {
final UpdateConfigRunnable runnable;
final Thread thread;

public NormalClass(Object object) {
    runnable = new UpdateConfigRunnable(object);
    this.thread = new Thread(runnable);
}

public void updateFile(String data) {
    runnable.setData(data);
    thread.start();
}
}

In the above implementation "NormalClass" is only ever accessed / called by one single thread. Calling the updateFile() method would then change the value of stringData inside the runnable object before I start the thread with the start() method. Is this the second example the correct way to go about this if not what did I get wrong about using multiple threads in this scenario?

Edit: Let me know if the following implementation of blocking queue is correct.

public class UpdateConfigRunnable implements Runnable {
private final BlockingQueue<ScreenGui> blockingQueue;

public UpdateConfigRunnable(final BlockingQueue<ScreenGui> blockingQueue) {
    this.blockingQueue = blockingQueue;
}

@Override
public void run() {
    while(true) {
        try {
            final ScreenGui screenGui = blockingQueue.take();

            final int sizeOfFont = screenGui.getSizeOfFont();
            final String font = screenGui.getFontName();

           //I will handle writing to the file here

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
}

Now here is the class which will be adding to queue. Here you can see I am actually adding a reference of this ScreenGui since that contains an object which I need to reference in order to obtain 2 final variables. Then I also need to pass in an this's instance of ScreenGui since in order for the runnable to have access sizeOfFont and fontName which I marked volatile since I want to keep those variables in ram to avoid compiler optimization (the values may be updated in cache but not in ram when viewed from the other thread). I could instead of passing in ScreenGui make a separate object which contains everything I need but I am not sure if generating a new object just for that is more optimized than just saving a larger object in memory aka ScreenGui.

final BlockingQueue<ScreenGui> blockingQueue;
final UpdateConfigRunnable runnable;
final Thread fileSaverThread;

private volatile String fontName;
private volatile int sizeOfFont;

public ScreenGui() {
    this.blockingQueue = new LinkedBlockingQueue<>();
    this.runnable = new UpdateConfigRunnable(blockingQueue);
    this.fileSaverThread = new Thread(runnable);
    fileSaverThread.setDaemon(true);
    fileSaverThread.start();
}

public void saveFile() {
    try {
        blockingQueue.put(this);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Also since the thread is a daemon thread I assume making an infinite while loop is fine inside of the runnable.

Upvotes: 1

Views: 908

Answers (2)

queeg
queeg

Reputation: 9372

Use one thread that keeps reading from a BlockingQueue and writing to the file. If there is nothing in the queue, the thread gets blocked by the BlockingQueue.take() method.

Now all your other threads have to do is to add data via the BlockingQueue.add() method.

Example:

public class ScreenGui {
    // contains the data that shall get written to the file
    // note that this class is a dumb data object - it does not create
    // any queues, threads or whatsoever
}

/** The FileWriter Thread. Shall be a singleton.
  */
public class Filer {
    private BlockingQueue<ScreenGui> bq;
    private Thread thread;

    private static Filer instance;
    private Filer() {
        bq = new BlockingQueue<>();
        thread = new Thread(()->{
            FileOutputStream fos = new FileOutputStream(...);
            while(true) {
                ScreenGui data = bq.take();
                
                // somehow write the ScreenGui to the output stream
            }
        });
        thread.start();
    }
    private static synchronized Filer getInstance() {
        if (instance == null) {
            instance = new Filer();
        }
    }
    public void write(ScreenGui data) {
        bq.put(data);
    }
}

/** The class that contains the actual application. It generates
  * the data that shall end up in the file.
  */
public class Application {
    public static void main(String[] args) {
        Filer filer = Filer.getInstance();  // with this the filer is initialized, it has a BlockingQueue and the background thread is waiting to write data

        // now either use this thread or more to write data
        filer.write(new ScreenGui());
    }
}

Upvotes: 5

Yasser CHENIK
Yasser CHENIK

Reputation: 407

Juste replace old thread with a new Thread inside updateFile and for each call, interrupt and cancel all changes of the old thread and replace it with a new one, with previous data that you canceled, to save it in the file with the new data you want to store for the current function call

I did a small implementation :

import java.io.*;


public class HelloWorld  {

    public volatile String oldFileData = "";
    private  Thread activeThread ;


    public HelloWorld(String init) {
        //inside the constructor read all the current file content iven if it's blocking because it will be done once and will be usefull for ever
        this.oldFileData = init;
    }

    public void updateLogFile(String toPrint){
        if(activeThread!=null && activeThread.isAlive()) {
            activeThread.interrupt();
            //replace all content in write mode
            this.activeThread = new Thread(() -> {
                try {
                    // replace all in that file with oldFileData
                    PrintWriter out = new PrintWriter(
                            new FileWriter("log.txt") // without true for write mode 
                    );
                    out.println(oldFileData + toPrint);//replace all
                    out.close();
                    oldFileData += toPrint;//if no exception this means the job is done and oldFileData must be updated
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            activeThread.start();
        }else{
            //add to previous data
            this.activeThread = new Thread(() -> {
                // open file in append mode to add and not replace
                try {
                    PrintWriter out = new PrintWriter(
                            new FileWriter("log.txt",true ) //USE TRUE FOR APPEND
                    );
                    out.println(toPrint);
                    out.close();
                    oldFileData += toPrint;//if no exception this means the job is done and oldFileData must be updated
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            activeThread.start();
        }



    }

    public static void main(String[] args) {
        HelloWorld hw = new HelloWorld("I m old content from the file");
        hw.updateLogFile("User clicked button1 \n");
        hw.updateLogFile("User clicked button2 ");

        System.out.println(hw.oldFileData);
    }

}

Upvotes: -1

Related Questions