4

I have a write method that is supposed to safely write data to a file.

// The current file I am writing to.
FileOutputStream file = null;
...
// Synchronized version.
private void write(byte[] bytes) {
  if (file != null && file.getChannel() != null) {
    try {
      boolean written = false;
      do {
        try {
          // Lock it!
          FileLock lock = file.getChannel().lock();
          try {
            // Write the bytes.
            file.write(bytes);
            written = true;
          } finally {
            // Release the lock.
            lock.release();
          }

        } catch (OverlappingFileLockException ofle) {
          try {
            // Wait a bit
            Thread.sleep(0);
          } catch (InterruptedException ex) {
            throw new InterruptedIOException("Interrupted waiting for a file lock.");
          }
        }
      } while (!written);
    } catch (IOException ex) {
      log.warn("Failed to lock " + fileName, ex);
    }
  } else {
    log.warn("Failing - " + (file == null ? "file" : "channel") + " is null!!");
  }
}

It has worked fine for me for a while now, although I know there are some wrinkles in it.

I have recently changed a project that uses this code to build and run under Java 5 (from Java 6) and now it looks like it is deadlocked awaiting a lock on the file. It is a multithreaded app and it is quite possible for several threads to attempt to write to the same file.

The debugger tells me that the hung threads are waiting for the FileLock lock = file.getChannel().lock() call to return.

Some research brought up this interesting little nugget which mentions:

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.

So am I doing it wrong? If so what is the right way? If I am doing it right how come I hit a deadlock?

Added: Forgot to mention - each thread holds its own copy of this object so there should not be any synchronisation issues within the code. I felt safe to rely on the FileChannel.lock() method to ensure writes do not interleave.

Added too: I have indeed solved the issue using various synchronized mechanisms. I do, however, have outstanding questions:

  1. Why is FileLock lock = file.getChannel().lock(); not suitable ...?
  2. Why did my issues only appear when switching back to Java-5 when everything worked fine with Java-6?
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Why are you locking the files if you are the only writer? What problem are you trying to solve? I wouldn't use locks as they can cause more problems than they solve. – Peter Lawrey Jul 18 '12 at 15:04
  • I am locking the file to avoid interleaving writes. What problems can file locks cause? – OldCurmudgeon Jul 18 '12 at 15:17

2 Answers2

4

FileLock is only for interprocess locking, javadoc reads:

"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."

To lock between java threads (same JVM) you need to use some shared lock. I would suggest within the file writing class to use a synchronized block (which according to these articles is likely to perform best):

final Object lock = new Object();

public void write(...){
  synchronized(lock){
    // do writing
  }
}

Another approach is to use a ReentrantLock and then use the proven idiom of

final ReentrantLock lock = new ReentrantLock();

public void write(...){
  try {
    lock.lock()
    // do the writing
  } finally {
    // forget this and you're screwed
    lock.unlock();
  }
}
cyber-monk
  • 5,470
  • 6
  • 33
  • 42
  • why use a ReentrantLock instead of a simple synchronized block? – jtahlborn Jul 18 '12 at 15:44
  • That is what I am beginning to believe:( So I need a lock for each file I write to, even though each file has a channel which can be locked! Do you know what it is about the channel lock that makes it unsuitable? – OldCurmudgeon Jul 18 '12 at 15:45
  • @jtahlborn - Synchronized will not help. The issue I use the lock to resolve is interleaving of writes, not concurrent access. – OldCurmudgeon Jul 18 '12 at 15:49
  • @jtahlborn synchronized block could work as well, ReentrantLock has a few more features. Recommend reading the class level javadoc: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantLock.html – cyber-monk Jul 18 '12 at 15:49
  • @OldCurmudgeon sorry that's beyond me, best I can do is refer to the javadoc. I welcome a more informed reader to chime in... – cyber-monk Jul 18 '12 at 15:53
  • @cyber-monk - i'm well aware of the features of ReentrantLock, none of which you are taking advantage of here. in your example, synchronized would achieve the exact same result with less boilerplate. – jtahlborn Jul 18 '12 at 17:12
  • @OldCurmudgeon - my original comment wasn't referring to the overall solution, just the code snippet posted in this answer. – jtahlborn Jul 18 '12 at 17:23
  • @jtahlborn as stated, above synchronized block would work as well and after reading this article I think it might even perform better: https://blogs.oracle.com/dave/entry/java_util_concurrent_reentrantlock_vs I'll add that as alternative – cyber-monk Jul 18 '12 at 19:03
  • I avoided the problem by protecting the code from multithread access. I still don't understand why a channel.lock is not an effective mechanism for protecting access to a file within the same VM. I would welcome further comments on that. – OldCurmudgeon Jul 22 '12 at 16:32
1

you may need to implement critical section concept on the actual code using the file, rather than the hashmap. You can either create a synchronized block or separate the file access code into a separate procedure and make that method synchronized.

Essentially, only one thread executes a synchronized block at a time. It gives you the exclusive access you need.

Another way of doing it is, to use a serial thread Executor, depending on your functional requirements.

You may want to look at this thread: Howto synchronize file access in a shared folder using Java (OR: ReadWriteLock on network level)

Community
  • 1
  • 1
srini.venigalla
  • 5,137
  • 1
  • 18
  • 29
  • 1
    My apologies - I have added to the question - there should not be any between-threads contention for this method. The lock is used to avoid interleaving writes. – OldCurmudgeon Jul 18 '12 at 15:03
  • No need to apologize good sir, but is the problem solved now? Basically, you should not expose the file handle outside a critical section. Define a critical block (method to write) instead of a critical resource (file). Each process calls the synchronized method, gets the connection, appends to the file, closes the connection, releases the block for the next guy. – srini.venigalla Jul 18 '12 at 15:20