2010-08-04 20 views
2

J'ai plusieurs threads qui sérialisent mes objets 'Data' dans des fichiers. Le nom de fichier est basé sur 2 champs de l'objetVerrouillage d'un fichier pour un thread individuel dans une machine virtuelle

 
    class Data { 
    org.joda.DateTime time; 
    String title; 

    public String getFilename() { 
     return time.toString() + '_' + title + ".xml"; 
    } 

Il est possible que 2 objets de données auront le même « temps » et « titre », et ainsi le même nom.

Ceci est acceptable, et je suis heureux que l'un ou l'autre soit enregistré. (Ils sont probablement le même objet Data de toute façon s'ils sont identiques)

Mon problème est que deux threads (ou plus) écrivent dans un fichier EN MÊME TEMPS, provoquant un XML malformé. J'ai jeté un oeil à java.nio.channels.FileLock, mais c'est pour le verrouillage VM-Wide, et en particulier PAS adapté pour le verrouillage intra-Thread.

Je pourrais synchroniser sur DataIO.class (mais cela entraînera un surcoût énorme, puisque je veux vraiment seulement synchroniser sur le fichier individuel).

La synchronisation sur l'objet Fichier sera inutile, car plusieurs objets Fichier peuvent représenter le même fichier système.

Code suit:

 
class DataIO { 
    public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException { 
    File file = new File(filename); 
    writeArticleToFile(article, file, overwrite); 
    } 

    public void writeDataToFile(Data data, File file, boolean overwrite) throws IOException { 
    if (file.exists()) { 
     if (overwrite) { 
     if (!file.delete()) { 
      throw new IOException("Failed to delete the file, for overwriting: " + file); 
     } 
     } else { 
     throw new IOException("File " + file + " already exists, and overwrite flag is set to false."); 
     } 
    } 

    File parentFile = file.getParentFile(); 
    if (parentFile != null) { 
     file.getParentFile().mkdirs(); 
    } 

    file.createNewFile(); 

    if (!file.canWrite()) { 
     throw new IOException("You do not have permission to write to the file: " + file); 
    } 

    FileOutputStream fos = new FileOutputStream(file, false); 
    try { 
     writeDataToStream(data, fos); 
     logger.debug("Successfully wrote Article to file: " + file.getAbsolutePath()); 
    } finally { 
     fos.close(); 
    } 
    } 
} 

Répondre

1

Vous stagiaire pourrait() la chaîne qui est le nom du fichier. Puis synchronisez sur la chaîne internée.

class DataIO { 
    public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException { 
    synchronized(filename.intern()) { 
     File file = new File(filename); 
     writeArticleToFile(article, file, overwrite); 
    } 
    } 
+0

Certainement la solution la plus simple. Pas sans risque cependant puisqu'il est préférable que les objets que vous verrouillez ne soient pas accessibles au public. (et une chaîne interne est toujours disponible n'importe où) –

+0

Ceci est une bonne et bonne pratique. Cependant, vous ne verrouillez pas la chaîne elle-même, mais plutôt en utilisant la chaîne pour former la base d'un verrou dans votre classe DataIO. Voir le post modifié. –

+0

C'est vraiment une solution très soignée, mais comme Kirk Woll dit que je pourrais avoir besoin de cette chaîne ailleurs (en particulier en lisant les fichiers etc). Cependant, si je préfixe le nom de fichier avec une chaîne inhabituelle et alambiquée (probablement le nom complet de la classe), puis verrouillez le intern() de THAT String, les chances de nécessiter cet objet String deviennent presque 0, ce qui est acceptable pour moi. – barryred

2

Si je lis correctement ceci, vous avez un objet de données qui représente un seul fichier.

Vous pouvez envisager de créer un ensemble agrégé en fonction de l'objet de données. Peut-être avoir un ConcurrentHashMap de

ConcurrentMap<Data,Lock> lockMap = new ConcurrentHashMap<Data,Lock>(); 

Non lorsque vous voulez écrire à cet objet que vous pouvez faire:

Lock lock = lockMap.get(someMyDataObject); 
lock.lock(); 
try{ 
    //write object here 
}finally{ 
    lock.unlock(); 
} 

Gardez à l'esprit que vous devez écrire le hashCode et égale méthode basée sur le titre et DateTime

0

Je suis d'accord que l'utilisation de la synchronisation est la technique que vous devez utiliser. Ce dont vous avez besoin est un objet distinct pour chaque permutation de fichier, et plus important encore le même objet à chaque fois. Une option pourrait être de créer une classe appelée FileLock:

public class FileLock { 
    DateTime time; 
    String title; 

    public FileLock(DateTime time, String title) { 
     this.time = time; 
     this.title = title; 
    } 

    override equals/hashCode based on those two properties 

    static Hashtable<FileLock, FileLock> unqiueLocks = new Hashtable<FileLock, FileLock>(); 
    static lockObject = new Object(); 

    public static FileLock getLock(DateTime time, String title) { 
     synchronized (lockObject) { 
      FileLock lock = new FileLock(time, title); 
      if (unqiueLocks.ContainsKey(lock)) { 
       return unqiueLocks.get(lock); 
      } 
      else { 
       unqiueLocks.put(lock, lock); 
       return lock; 
      } 
     } 
    } 
} 

Ensuite, les appelants l'utiliseraient comme:

synchronized (FileLock.getLock(time, title)) { 
    ... 
} 

Gardez à l'esprit ce qui a une fuite de mémoire depuis le Hashtable ne cesse de croître avec le nouveau fichier/heure permutations. Si vous en avez besoin, vous pouvez modifier cette technique pour que les appelants de getLock appellent également une méthode releaseLock que vous utilisez pour garder le Hashtable propre.