Class SynchronizedFile
When multiple threads or processes access the same file, they would require some form of synchronization. This class provides a simple way for the clients to add synchronization capability to a file without having to work with low-level details involving single-process or multi-process locking.
Synchronization can take effect for threads within the same process or across different
processes. The client can configure this locking scope when constructing a
SynchronizedFile. If the file is never accessed by more than one process at a time, the client
should configure the file with SINGLE_PROCESS locking scope since there will be less
synchronization overhead. However, if the file may be accessed by more than one process at a
time, the client must configure the file with MULTI_PROCESS locking scope.
In any case, synchronization takes effect only for the same file (i.e., threads/processes
accessing different files are not synchronized). Also, the client must access the file via
SynchronizedFile's API; otherwise, the previous concurrency guarantees will not hold.
Two files are considered the same if they refer to the same physical file. There could be
multiple instances of SynchronizedFile for the same physical file, and as long as they
refer to the same physical file, access to them will be synchronized.
Once the SynchronizedFile is constructed, the client can read or write to the file as
follows.
boolean fileExists = synchronizedFile.read(file -> file.exists());
boolean result = synchronizedFile.write(file -> { Files.touch(file); return true; });
Multiple threads/processes can read the same file at the same time. However, once a
thread/process starts writing to the file, the other threads/processes will block. This behavior
is similar to a ReadWriteLock.
Additionally, this class provides the createIfAbsent(ExceptionConsumer) method for
the client to atomically create the file if it does not yet exist.
Note that we often use the term "process", although the term "JVM" would be more correct since there could exist multiple JVMs in a process.
This class is thread-safe.
-
Method Summary
Modifier and TypeMethodDescriptionvoidcreateIfAbsent(ExceptionConsumer<File> action) Executes an action that creates the file if it does not yet exist.static SynchronizedFilegetInstanceWithMultiProcessLocking(File fileToSynchronize) Returns aSynchronizedFileinstance for the given file, where synchronization on the same file takes effect for threads both within the same process and across different processes (two files are the same if they refer to the same physical file).static SynchronizedFilegetInstanceWithSingleProcessLocking(File fileToSynchronize) Returns aSynchronizedFileinstance for the given file, where synchronization on the same file takes effect for threads within the same process but not for threads across different processes (two files are the same if they refer to the same physical file).static FilegetLockFile(File fileToSynchronize) Returns the path to the lock file that has been or will be created next to the file being synchronized under the same parent directory.<V> Vread(ExceptionFunction<File, V> action) Executes an action that reads the file with a SHARED lock.toString()<V> Vwrite(ExceptionFunction<File, V> action) Executes an action that writes to (or deletes) the file with an EXCLUSIVE lock.
-
Method Details
-
getInstanceWithMultiProcessLocking
@NonNull public static SynchronizedFile getInstanceWithMultiProcessLocking(@NonNull File fileToSynchronize) Returns aSynchronizedFileinstance for the given file, where synchronization on the same file takes effect for threads both within the same process and across different processes (two files are the same if they refer to the same physical file).Inter-process synchronization is provided via
ReadWriteProcessLock, which requires a lock file to be created. This lock file is different from the file being synchronized and will be placed next to that file under the same parent directory.The file being synchronized and the lock file may or may not exist when this method is called. The lock file will be created if it does not yet exist and will not be deleted after this method is called.
In order for the lock file to be created (if it does not yet exist), the parent directory of the file being synchronized and the lock file must exist when this method is called.
IMPORTANT: The lock file must be used solely for synchronization purposes. The client of this class must not access (read, write, or delete) the lock file. The client may delete the lock file only when the locking mechanism is no longer in use.
This method will normalize the file's path first to detect same physical files via equals(), so the client does not need to normalize the file's path in advance.
Note: If the file is never accessed by more than one process at a time, the client should use the
getInstanceWithSingleProcessLocking(File)method instead since there will be less synchronization overhead.- Parameters:
fileToSynchronize- the file whose access will be synchronized; it may not yet exist, but its parent directory must exist- See Also:
-
getInstanceWithSingleProcessLocking
@NonNull public static SynchronizedFile getInstanceWithSingleProcessLocking(@NonNull File fileToSynchronize) Returns aSynchronizedFileinstance for the given file, where synchronization on the same file takes effect for threads within the same process but not for threads across different processes (two files are the same if they refer to the same physical file).The file being synchronized may or may not exist when this method is called.
This method will normalize the file's path first to detect same physical files via equals(), so the client does not need to normalize the file's path in advance.
Note: If the file may be accessed by more than one process at a time, the client must use the
getInstanceWithMultiProcessLocking(File)method instead.- Parameters:
fileToSynchronize- the file whose access will be synchronized, which may not yet exist- See Also:
-
getLockFile
Returns the path to the lock file that has been or will be created next to the file being synchronized under the same parent directory.- Parameters:
fileToSynchronize- the file whose access is synchronized, which may not yet exist- Returns:
- the lock file, which may not yet exist
-
read
Executes an action that reads the file with a SHARED lock.- Parameters:
action- the action that will read the file- Returns:
- the result of the action
- Throws:
ExecutionException- if an exception occurred during the execution of the actionRuntimeException- if a runtime exception occurred, but not during the execution of the action
-
write
Executes an action that writes to (or deletes) the file with an EXCLUSIVE lock.- Parameters:
action- the action that will write to (or delete) the file- Returns:
- the result of the action
- Throws:
ExecutionException- if an exception occurred during the execution of the actionRuntimeException- if a runtime exception occurred, but not during the execution of the action
-
createIfAbsent
Executes an action that creates the file if it does not yet exist. This method is performed atomically.This method throws a
RuntimeExceptionif the file does not exist but the action does not create the file.WARNING: It is not guaranteed that the file must exist after this method is executed, since another thread/process might delete it after this method returns (known as the TOCTTOU problem). Therefore, if a client wants to use this method to make sure the file exists for a subsequent action, it must also make sure that no intervening thread/process may be deleting the file after it is created.
- Parameters:
action- the action that will create the file- Throws:
ExecutionException- if an exception occurred during the execution of the actionRuntimeException- if a runtime exception occurred, but not during the execution of the action
-
toString
-