How to use mkdirs in a thread safe manner in Java?
After exper开发者_如何学Ciencing issues with mkdirs() and poking around the interwebs, I get the impression that there are thread safety issues with mkdirs().
Is there a way to ensure the directories are properly created when it is possible that multiple threads might be trying to create similar file structures?
Thanks
(In my case I will be using this on Android)
I'm not sure if Android supports the concurrent package but here is my take:
private static Lock fsLock = new ReentrantLock();
private void mkdir( File dir ) throws FileNotFoundException {
if( dir.exists() ) {
return;
}
fsLock.lock();
try {
if( !dir.exists() ) {
log.info( "Creating directory {}", dir.getAbsolutePath() );
if( !dir.mkdirs() ) {
throw new FileNotFoundException( "Can't create directory " + dir.getAbsolutePath() );
}
}
} finally {
fsLock.unlock();
}
}
The method returns early if the directory already exists. If it doesn't exist, only one thread will try to create it.
Do all your directory creation in a worker thread that serializes everything. You can use a Looper
and a Handler
to make it easy to post Runnables
that call mkdirs to your worker thread. When you're done making directories, you can call Looper.quit() to end the thread after it processes the last posted Runnable
. The documentation for Looper
has sample code that shows how near to trivial this is to do.
One possible solution would be a MkDirService (illustrated below) that guarantees only one instance and runs in it's own thread. Making use of BlockingQueue.
First the Service:
package mkdir;
import java.io.File;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MkDirService extends Thread {
private static MkDirService service;
private BlockingQueue<File> pendingDirs = new LinkedBlockingQueue<File>();
private boolean run = true;
private MkDirService() {
}
public synchronized static MkDirService getService() {
if (service == null) {
service = new MkDirService();
new Thread(service).start();
}
return service;
}
public void makeDir(File dir) {
pendingDirs.add(dir);
}
public void shutdown() {
run = false;
}
@Override
public void run() {
while (run || !pendingDirs.isEmpty()) {
File curDir = null;
try {
curDir = pendingDirs.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (curDir != null && !curDir.exists()) {
curDir.mkdir();
System.out.println("Made: " + curDir.getAbsolutePath());
}
}
}
}
The the Test:
package mkdir;
import java.io.File;
public class MkDirServiceTest {
/**
* @param args
*/
public static void main(String[] args) {
MkDirService mdServ = MkDirService.getService();
mdServ.makeDir(new File("test1"));
mdServ.makeDir(new File("test1/test2"));
mdServ.makeDir(new File("test1/test3"));
mdServ.shutdown();
}
}
Okay, I know this has been inactive for a while, but I thought perhaps there was a simple solution. The article you linked in the comments on the question seems to indicate that the only problem is directories not being created. The solution there was to do this:
if (!f.mkdirs()) {
f.mkdirs();
}
However, that seems inefficient and can still have problems. So, why not simply do this:
while (!f.mkdirs()) {}
Simple, but it works.
EDIT: After thinking a bit, that example may lag to oblivion and could cause thread lock. So, this might be a better idea:
while (!f.mkdirs()) { Thread.yield(); }
Of course, that would only be recommended if you're in a thread that could cause thread lock, and as long as it's not a high-priority situation. Just putting this out there.
Eaven if this thread is a bit older I wonder if there is somethink wrong with the following solution:
package service;
import java.io.File;
public class FileService {
public static synchronized boolean mkdirs( File dir ) {
return dir.mkdirs();
}
}
精彩评论