FileLock是文件锁,它能保证同一时间只有一个进程(程序)能够修改它,或者都只可以读,这样就解决了多进程间的同步文件,保证了安全性。但是需要注意的是,它进程级别的,不是线程级别的,他可以解决多个进程并发访问同一个文件的问题,但是它不适用于控制同一个进程中多个线程对一个文件的访问。这也是为什么它叫做 多进程文件锁,而不是 多线程文件锁。
FileLock一般都是从FileChannel 中获取,FileChannel 提供了三个方法用以获取 FileLock。
- lock() 是阻塞式的,它要阻塞进程直到锁可以获得,或调用
lock()
的线程中断,或调用lock()
的通道关闭。 - tryLock()是非阻塞式的,它设法获取锁,但如果不能获得,例如因为其他一些进程已经持有相同的锁,而且不共享时,它将直接从方法调用返回。
position:锁定文件中的开始位置
size:锁定文件中的内容长度
shared:是否使用共享锁。true为共享锁;false为独占锁。
/** * 先运行 * @throws Exception */ @Test public void FileWriteTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); System.out.println("进程 1 开始写内容:" + LocalTime.now()); for (int i = 1; i <= 10; i++) { randomAccessFile.writeUTF("VipSoft_" + i); System.out.println("writeChars:" + i); // 等待两秒 TimeUnit.SECONDS.sleep(2); } System.out.println("进程 1 完成写内容:" + LocalTime.now()); fileChannel.close(); randomAccessFile.close(); } /** * 再运行,看读出文件大小的变化 * @throws Exception */ @Test public void FileReadTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); System.out.println("开始读文件的时间:" + LocalTime.now()); for (int i = 0; i < 10; i++) { // FileWriteTest() 运行后,运行 FileReadTest(),发现文件大小在变。说明文件在一边读一边写 System.out.println("文件大小为:" + randomAccessFile.length()); // 这里等待 1 秒 TimeUnit.SECONDS.sleep(1); } System.out.println("结束读文件的时间:" + LocalTime.now()); fileChannel.close(); randomAccessFile.close(); }
使用文件锁,使用后,只能等锁释放掉以后,另一个进程才能对期进行操作
/** * 先运行 * @throws Exception */ @Test public void FileWriteTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock = fileChannel.lock(); System.out.println("进程 1 开始写内容:" + LocalTime.now()); for (int i = 1; i <= 10; i++) { randomAccessFile.writeUTF("VipSoft_" + i); System.out.println("writeChars:" + i); // 等待两秒 TimeUnit.SECONDS.sleep(2); } System.out.println("进程 1 完成写内容:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); } /** * 再运行,看读出文件大小的变化 * @throws Exception */ @Test public void FileReadTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock = fileChannel.lock(); System.out.println("开始读文件的时间:" + LocalTime.now()); for (int i = 0; i < 10; i++) { // FileWriteTest() 运行后,运行 FileReadTest(),发现文件大小在变。说明文件在一边读一边写 System.out.println("文件大小为:" + randomAccessFile.length()); // 这里等待 1 秒 TimeUnit.SECONDS.sleep(1); } System.out.println("结束读文件的时间:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); }
同进程不同线程进行文件读写
FileLock是不适用同一进程不同线程之间文件的访问。因为你根本无法在一个进程中不同线程同时对一个文件进行加锁操作,如果线程1对文件进行了加锁操作,这时线程2也来进行加锁操作的话,则会直接抛出异常:java.nio.channels.OverlappingFileLockException
import org.junit.Test; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.time.LocalTime; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class FileLockTest { @Test public void RunnableTest() throws InterruptedException { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); Thread thread1 = new Thread(runner1); Thread thread2 = new Thread(runner2); thread1.start(); thread2.start(); System.out.print("阻塞当前线程,直到倒数计数器倒数到0"); new CountDownLatch(1).await(); } class Runner1 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { try { FileWriteTest(); } catch (Exception e) { e.printStackTrace(); } } } class Runner2 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { try { FileReadTest(); } catch (Exception e) { e.printStackTrace(); } } } /** * 先运行 * @throws Exception */ public void FileWriteTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock = fileChannel.lock(); System.out.println("进程 1 开始写内容:" + LocalTime.now()); for (int i = 1; i <= 10; i++) { randomAccessFile.writeUTF("VipSoft_" + i); System.out.println("writeChars:" + i); // 等待两秒 TimeUnit.SECONDS.sleep(2); } System.out.println("进程 1 完成写内容:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); } /** * 再运行,看读出文件大小的变化 * @throws Exception */ @Test public void FileReadTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock = fileChannel.lock(); System.out.println("开始读文件的时间:" + LocalTime.now()); for (int i = 0; i < 10; i++) { // FileWriteTest() 运行后,运行 FileReadTest(),发现文件大小在变。说明文件在一边读一边写 System.out.println("文件大小为:" + randomAccessFile.length()); // 这里等待 1 秒 TimeUnit.SECONDS.sleep(1); } System.out.println("结束读文件的时间:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); } }
可以通过另外一种方式来规避程序异常,如下:
FileLock fileLock; while (true){ try{ fileLock = fileChannel.tryLock(); break; } catch (Exception e) { System.out.println("这里是 FileWriteTest ,其他线程已经获取该文件锁了,当前线程休眠 2 秒再获取"); TimeUnit.SECONDS.sleep(2); } }
完整DEMO如下
package org.apache.rocketmq.store; import org.junit.Test; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.time.LocalTime; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class FileLockTest { @Test public void RunnableTest() throws InterruptedException { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); Thread thread1 = new Thread(runner1); Thread thread2 = new Thread(runner2); thread1.start(); thread2.start(); System.out.print("阻塞当前线程,直到倒数计数器倒数到0"); new CountDownLatch(1).await(); } class Runner1 implements Runnable { public void run() { try { FileWriteTest(); } catch (Exception e) { e.printStackTrace(); } } } class Runner2 implements Runnable { public void run() { try { FileReadTest(); } catch (Exception e) { e.printStackTrace(); } } } /** * 先运行 * @throws Exception */ public void FileWriteTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock; while (true){ try{ fileLock = fileChannel.tryLock(); break; } catch (Exception e) { System.out.println("这里是 FileWriteTest ,其他线程已经获取该文件锁了,当前线程休眠 2 秒再获取"); TimeUnit.SECONDS.sleep(2); } } System.out.println("进程 1 开始写内容:" + LocalTime.now()); for (int i = 1; i <= 10; i++) { randomAccessFile.writeUTF("VipSoft_" + i); System.out.println("writeChars:" + i); // 等待两秒 TimeUnit.SECONDS.sleep(2); } System.out.println("进程 1 完成写内容:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); } /** * 再运行,看读出文件大小的变化 * @throws Exception */ @Test public void FileReadTest() throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\temp\\filelock.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); // 这里是独占锁 FileLock fileLock; while (true){ try{ fileLock = fileChannel.tryLock(); break; } catch (Exception e) { System.out.println("这里是 FileReadTest,其他线程已经获取该文件锁了,当前线程休眠 2 秒再获取"); TimeUnit.SECONDS.sleep(2); } } System.out.println("开始读文件的时间:" + LocalTime.now()); for (int i = 0; i < 10; i++) { // FileWriteTest() 运行后,运行 FileReadTest(),发现文件大小在变。说明文件在一边读一边写 System.out.println("文件大小为:" + randomAccessFile.length()); // 这里等待 1 秒 TimeUnit.SECONDS.sleep(1); } System.out.println("结束读文件的时间:" + LocalTime.now()); // 完成后要释放掉锁 fileLock.release(); fileChannel.close(); randomAccessFile.close(); } }