¿Cómo puedo bloquear un archivo usando java (si es posible)?

121

Tengo un proceso de Java que abre un archivo usando un FileReader. ¿Cómo puedo evitar que otro proceso (Java) abra este archivo, o al menos notificar a ese segundo proceso que el archivo ya está abierto? ¿Esto automáticamente hace que el segundo proceso obtenga una excepción si el archivo está abierto (lo que resuelve mi problema) o tengo que abrirlo explícitamente en el primer proceso con algún tipo de indicador o argumento?

Para aclarar:

Tengo una aplicación Java que enumera una carpeta y abre cada archivo en la lista para procesarlo. Procesa cada archivo tras otro. El procesamiento de cada archivo consiste en leerlo y hacer unos cálculos basados ​​en el contenido y demora unos 2 minutos. También tengo otra aplicación Java que hace lo mismo, pero escribe en el archivo. Lo que quiero es poder ejecutar estas aplicaciones al mismo tiempo para que el escenario sea así. ReadApp enumera la carpeta y busca los archivos A, B, C. Abre el archivo A e inicia la lectura. WriteApp enumera la carpeta y encuentra los archivos A, B, C.Abre el archivo A, ve que está abierto (por una excepción o de cualquier manera) y va al archivo B. ReadApp finaliza el archivo A y continúa hasta B. Ve que está abierto y continúa en C. Es crucial que WriteApp no t escriba mientras ReadApp está leyendo el mismo archivo o viceversa. Son procesos diferentes.

Paralife
fuente
12
¿Quiere decir 'proceso' como en proceso (dos JVM) o subproceso (la misma JVM). El impacto en la respuesta es primordial.
Stu Thompson
verifique el código de muestra que muestra la solución aquí: stackoverflow.com/a/58871479/5154619
Davi Cavalcanti

Respuestas:

117

FileChannel.lock es probablemente lo que desea.

try (
    FileInputStream in = new FileInputStream(file);
    java.nio.channels.FileLock lock = in.getChannel().lock();
    Reader reader = new InputStreamReader(in, charset)
) {
    ...
}

(Descargo de responsabilidad: código no compilado y ciertamente no probado).

Tenga en cuenta la sección titulada "dependencias de la plataforma" en el documento API para FileLock .

Tom Hawtin - tackline
fuente
22
Más importante aún, comprenda que el bloqueo de la JVM no es adecuado para bloquear el archivo para el acceso de subprocesos individuales dentro de una sola JVM.
Stu Thompson
11
Necesita una secuencia de escritura (es decir FileOutputStream).
Javier
@Javier ¿Tú? No lo he intentado. Nada salta de los documentos de la API diciendo que es un requisito. FileOutputStreamno será de mucha utilidad para un Reader.
Tom Hawtin - táctica
18
Sí, lo probé y arroja NonWritableChannelException, porque lock()intenta adquirir un bloqueo exclusivo, pero eso requiere acceso de escritura. Si tiene un flujo de entrada , puede usar el lock(0L, Long.MAX_VALUE, false)que adquiere un bloqueo compartido y solo requiere un acceso de lectura. También puede usar un modo RandomAccessFileabierto en lectura-escritura si desea un bloqueo exclusivo mientras lee ... pero eso prohibiría los lectores concurrentes.
Javier
6
@Javier Creo que quieres decir lock(0L, Long.MAX_VALUE, true), no lock(0L, Long.MAX_VALUE, false). el último argumento es boolean shared docs.oracle.com/javase/8/docs/api/java/nio/channels/…
john sullivan
60

No use las clases en el java.iopaquete, en su lugar use el java.niopaquete. Este último tiene una FileLockclase. Puede aplicar un bloqueo a un FileChannel.

 try {
        // Get a file channel for the file
        File file = new File("filename");
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

        // Use the file channel to create a lock on the file.
        // This method blocks until it can retrieve the lock.
        FileLock lock = channel.lock();

        /*
           use channel.lock OR channel.tryLock();
        */

        // Try acquiring the lock without blocking. This method returns
        // null or throws an exception if the file is already locked.
        try {
            lock = channel.tryLock();
        } catch (OverlappingFileLockException e) {
            // File is already locked in this thread or virtual machine
        }

        // Release the lock - if it is not null!
        if( lock != null ) {
            lock.release();
        }

        // Close the file
        channel.close();
    } catch (Exception e) {
    }
ayengin
fuente
Por cierto, estoy escribiendo en el archivo de bloqueo el pid actual de esta sugerencia stackoverflow.com/a/35885/1422630 , ¡así que después puedo leerlo en la nueva instancia!
Aquarius Power
1
este se veía bien, pero no funciona. Recibo la excepción OverlappingFileLockException cada vez, incluso cuando el archivo ni siquiera existía
Gavriel
1
Ocurrirá un problema si llamas a tryLock después de bloquear como está escrito en el ejemplo
Igor Vuković
17

Si puede usar Java NIO ( JDK 1.4 o superior ), creo que está buscandojava.nio.channels.FileChannel.lock()

FileChannel.lock ()

KC Baltz
fuente
5
Tal vez. Depende de lo que OP signifique por "proceso". "Los bloqueos de archivos se mantienen en nombre de toda la máquina virtual Java. No son adecuados para controlar el acceso a un archivo por varios subprocesos dentro de la misma máquina virtual".
Stu Thompson
@Stu: Sé que ha respondido a esta pregunta hace mucho tiempo, pero espero que pueda explicar lo que quiere decir cuando dijoFile 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
Thang Pham
3
@Harry Está citando de los documentos: download.oracle.com/javase/6/docs/api/java/nio/channels/… Significa que es invisible para los hilos pero afecta a otros procesos.
Artur Czajka
@Harry: Para agregar aún más a estos necro-comentarios, imagina que estás usando Java para servir sitios web con Tomcat. Es posible que tenga muchos hilos, cada uno de los cuales atiende una solicitud desde un navegador web. Sin embargo, todos controlan el mismo mecanismo de bloqueo de archivos como demasiados cocineros en una cocina. Una solicitud puede terminar en medio de una segunda y, de repente, su archivo se "desbloqueó" mientras aún estaba en medio de algo, y luego algún otro proceso como un cronjob podría bloquearlo, y luego inesperadamente perdió su bloquear y su solicitud no puede terminar ...
Darien
5

Puede que esto no sea lo que está buscando, pero con el interés de abordar un problema desde otro ángulo ...

¿Son estos dos procesos Java los que podrían querer acceder al mismo archivo en la misma aplicación? ¿Quizás puede filtrar todo el acceso al archivo a través de un único método sincronizado (o, mejor aún, usando JSR-166 )? De esa forma, puede controlar el acceso al archivo y tal vez incluso poner en cola las solicitudes de acceso.

pkaeding
fuente
3
Dos procesos no pueden usar la sincronización, solo dos subprocesos en el mismo proceso.
Marqués de Lorne
3

Use un RandomAccessFile, obtenga su canal, luego llame a lock (). El canal proporcionado por los flujos de entrada o salida no tiene suficientes privilegios para bloquearse correctamente. Asegúrese de llamar a unlock () en el bloque finalmente (cerrar el archivo no necesariamente libera el bloqueo).

Kevin Day
fuente
¿puedes elaborar? Quiero decir, ¿hasta qué punto el bloqueo de RandomAccess File es mejor o más seguro que el de streams uno?
Paralife
Enlace a un ejemplo simple publicado a continuación
Touko
Paralife, perdón por el retraso, acaba de notar tu pregunta. Los bloqueos de los flujos serán bloqueos de lectura (para flujos de entrada) y bloqueos de escritura de canal completo exclusivos (para flujos de salida). Mi experiencia ha sido que los bloqueos de RAF permiten un control más detallado (es decir, puede bloquear partes de un archivo).
Kevin Day
1

A continuación se muestra un código de fragmento de muestra para bloquear un archivo hasta que JVM realice su proceso.

 public static void main(String[] args) throws InterruptedException {
    File file = new File(FILE_FULL_PATH_NAME);
    RandomAccessFile in = null;
    try {
        in = new RandomAccessFile(file, "rw");
        FileLock lock = in.getChannel().lock();
        try {

            while (in.read() != -1) {
                System.out.println(in.readLine());
            }
        } finally {
            lock.release();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
Ajay Kumar
fuente