How to synchronize file access in a Java servlet?
Asked Answered
H

3

5

I created a small Java servlet for a simple purpose: Once it is called, it will do the following steps:

  1. Read file foo.json from the local filesystem
  2. Process the data from the file and do some changes to it
  3. Write back the changes to the file

Simplified version of the code:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    FileInputStream inputStream = new FileInputStream("foo.json");
    String filecontent = IOUtils.toString(inputStream);
    inputStream.close();

    JSONObject json = new JSONObject(filecontent);

    doSomeChangesTo(json);

    FileWriter writer = new FileWriter("foo.json");
    writer.write(json.toJSONString());
    writer.flush();
    writer.close();
}

Now I am facing the problem that it could happen that the servlet is called nearly at the same time by two or more http requests to the servlet. To avoid multiple parallel write access on the same file I need to synchronize this somehow. From my understanding of the servlet lifecycle process, each request spawns a new thread, so using FileLock would probably have no affect:

File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine.

(From http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html)

I guess that using the synchronized(){} keyword would also not work since I want to synchronize file system access and not access to variables/objects.

So, how can synchronize file system access in my servlet when multiple parallel requests on that servlet happen?

Hammers answered 28/8, 2014 at 11:55 Comment(7)
Rename the file while working on it? I guess using a DB instead of a file is not a decision that's up to you to make?Colwin
If your app is the only one to write to the file (and is not clustered), synchonization would work fine. People usually use a database which handles concurrent access for you.Maquis
What exactly are you trying to synchronize here? Is it the entire procedure of read-process-write? Or is it the individual reads and writes, while the processing can intertwine (And you may write back data from an old read)?Mile
@Colwin Correct, using a db is currently no optionHammers
@JBNizet So I simply wrap the code I posted into a synchronized(){} and that's it?Hammers
@Ordeous I basically want to lock access to the file before I read the file, then do the changes, then update the file and the release the lock.Hammers
Initialize a static final Object FILE_ACCES_LOCK = new Object(), and wrap the code accessing the file in synchronized(FILE8ACCESS8LOCK) {}Maquis
P
8

I guess that using the synchronized(){} keyword would also not work since I want to synchronize file system access and not access to variables/objects.

Using synchronized can work. You are assuming that if you want to control access to object X from multiple threads, you must use synchronized on that object. You don't. You can use synchronized on any object, provided all the accesses use the same object.

In fact, it is often better to use a separate private lock object for synchronization, because it is then impossible for code outside the class to synchronize on the lock.

So , you might have something like this, with one instance for each shared file:

 public class SharedFile
 {
      private final File path;
      private final Object lock = new Object();

      public SharedFile(File path) {
         this.path = path;
      }

      public void process(.....) throws IOException {
         synchronized(lock) {
            try(InputStream = new FileInputStream(path)) {
               ....
            }
         }
      }
 }
Pretypify answered 28/8, 2014 at 12:5 Comment(1)
No, the lock object is the important hereVishinsky
M
3

You can use a Semaphore, as follows:

private static Semaphore semaphore = new Semaphore(1);

public void doSomeChangesTo(JSONObject json) {
    try {
        semaphore.acquire();

        // doSomeChangesTo

    } finally {
        semaphore.release();
    }
}
Marlenmarlena answered 28/8, 2014 at 12:9 Comment(2)
Can I use this concept even if the object is being used by multiple child classes? @MarlenmarlenaChilt
Hi @hanish! Not sure if I get what you men but I don't see anything wrong with that.Marlenmarlena
H
0

You should use a Semaphore to protect access to the resource. (Your file.) See http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html

Homocentric answered 28/8, 2014 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.