Java读取文件加锁代码Demo(利用Java的NIO)

本文深入探讨Java中的FileLock类,讲解其在并发控制中的作用,包括共享锁与独占锁的概念,lock()与tryLock()的区别,以及如何避免死锁。适用于希望了解Java文件同步机制的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 学习笔记,转自:https://www.cnblogs.com/DreamDrive/p/7425281.html

 

Java 提供了文件锁FileLock类,利用这个类可以控制不同程序(JVM)对同一文件的并发访问,实现进程间文件同步操作。
FileLock是Java 1.4 版本后出现的一个类,它可以通过对一个可写文件(w)加锁,保证同时只有一个进程可以拿到文件的锁,这个进程从而可以对文件做访问;而其它拿不到锁的进程要么选择被挂起等待,要么选择去做一些其它的事情, 这样的机制保证了众进程可以顺序访问该文件。也可以看出,能够利用文件锁的这种性质,在一些场景下,虽然我们不需要操作某个文件, 但也可以通过 FileLock 来进行并发控制,保证进程的顺序执行,避免数据错误。
“Locks are associated with files, not channels. Use locks to coordinate with external processes, not between threads in the same JVM.”
1. 概念
共享锁: 共享读操作,但只能一个写(读可以同时,但写不能)。共享锁防止其他正在运行的程序获得重复的独占锁,但是允许他们获得重复的共享锁。
独占锁: 只有一个读或一个写(读和写都不能同时)。独占锁防止其他程序获得任何类型的锁。

2. FileLock FileChannel.lock(long position, long size, boolean shared)
shared的含义:是否使用共享锁,一些不支持共享锁的操作系统,将自动将共享锁改成排它锁。可以通过调用isShared()方法来检测获得的是什么类型的锁。

3. lock()和tryLock()的区别:
lock()阻塞的方法,锁定范围可以随着文件的增大而增加。无参lock()默认为独占锁;有参lock(0L, Long.MAX_VALUE, true)为共享锁。
tryLock()非阻塞,当未获得锁时,返回null.

4. FileLock的生命周期:在调用FileLock.release(),或者Channel.close(),或者JVM关闭

5. FileLock是线程安全的

6. 注意事项:
同一进程内,在文件锁没有被释放之前,不可以再次获取。即在release()方法调用前,只能lock()或者tryLock()一次。
文件锁的效果是与操作系统相关的。一些系统中文件锁是强制性的,就当Java的某进程获得文件锁后,操作系统将保证其它进程无法对文件做操作了。而另一些操作系统的文件锁是询问式的(advisory),意思是说要想拥有进程互斥的效果,其它的进程也必须也按照API所规定的那样来申请或者检测文件锁,不然将起不到进程互斥的功能。所以文档里建议将所有系统都当做是询问式系统来处理,这样程序更加安全也更容易移植。
如何避免死锁:在读写关键数据时加锁,操作完成后解锁;一次性申请所有需要的资源,并且在申请不成功的情况下放弃已申请到的资源。

 

Java文件锁定一般都通过FileChannel来实现。主要涉及如下2个方法:
tryLock() throws IOException  试图获取对此通道的文件的独占锁定。
tryLock(long position, long size, boolean shared) throws IOException  试图获取对此通道的文件给定区域的锁定。
tryLock等同于tryLock(0L, Long.MAX_VALUE, false) ,它获取的是独占锁,所以一定是在释放锁之后,才能读取到文件内容。

 

 

package com.example.demo.service.impl; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.dao.entity.WExecuteHost; import com.example.demo.dao.entity.WPersonalHost; import com.example.demo.dao.mapper.WExecuteHostMapper; import com.example.demo.service.WExecuteHostService; import com.example.demo.service.WPersonalHostService; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * @author fangpeiyuan * @description 针对表【w_execute_host(执行主机表)】的数据库操作Service实现 * @createDate 2025-06-06 09:32:45 */ @Service public class WExecuteHostServiceImpl extends ServiceImpl<WExecuteHostMapper, WExecuteHost> implements WExecuteHostService { @Autowired private WPersonalHostService wPersonalHostService; @Data private static class UserSession { private Process cmdProcess; private BufferedWriter commandWriter; private volatile boolean isProcessRunning; private String sessionId; // 唯一会话ID } // 替换全局变量为会话映射 private final Map<String, UserSession> userSessions = new ConcurrentHashMap<>(); private final SimpMessagingTemplate messagingTemplate; public WExecuteHostServiceImpl(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; } @Override public String pipList() { return null; } @Override public void executeCommand(String command, String executeHost, String userId) { // 判断这个host是否属于该用户并状态是活跃的 LambdaQueryWrapper<WPersonalHost> queryWrapper = new LambdaQueryWrapper<>(); //todo 后续动态 queryWrapper .eq(WPersonalHost::getP13, userId) .eq(WPersonalHost::getExecuteHost, executeHost) .eq(WPersonalHost::getState, "在线"); WPersonalHost wPersonalHost = wPersonalHostService.getOne(queryWrapper); if (ObjectUtil.isEmpty(wPersonalHost)) { System.out.println("该主机并不属于当前用户,请联系管理员处理。"); return; } // 初始化日志目录 // todo 后面动态 String logDir = System.getProperty("user.dir") + File.separator + "command-log" + File.separator + userId + File.separator + executeHost + File.separator + "项目名称"; // 创建目录(如果不存在) File logDirectory = new File(logDir); if (!logDirectory.exists()) { boolean created = logDirectory.mkdirs(); if (!created) { System.err.println("无法创建日志目录: " + logDir); return; // 或者抛出异常 } System.out.println("日志目录已创建: " + logDir); } // 按天生成日志文件名 String dateStr = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); String logFilePath = logDir + File.separator + dateStr + ".log"; try { // 确保日志目录存在 Files.createDirectories(Paths.get(logDir)); // 初始化日志文件写入器(追加模式) BufferedWriter logWriter = new BufferedWriter(new FileWriter(logFilePath, true)); // 记录开始时间 logWriter.write("\n———————— 执行时间 " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " ————————\n"); // ABORT 命令处理 if ("ABORT".equalsIgnoreCase(command)) { handleAbort(userId); // 修改为接收 userId return; } // 获取或创建用户专属会话 UserSession session = userSessions.computeIfAbsent(userId, key -> { if (userSessions.containsKey(userId)) { sendError("已有命令执行中,请等待完成", userId); return null; } try { UserSession newSession = new UserSession(); newSession.setSessionId(UUID.randomUUID().toString()); // 初始化专属进程 (关键修改点) String uniqueTitle = "User_" + userId + "_" + System.currentTimeMillis(); newSession.setCmdProcess(new ProcessBuilder("cmd.exe", "/k", "title " + uniqueTitle) .redirectErrorStream(true).start()); newSession.setCommandWriter( new BufferedWriter(new OutputStreamWriter( newSession.getCmdProcess().getOutputStream(), "gbk")) ); // 启动专属输出线程 (使用当前 userId) startOutputThread(newSession, userId, logFilePath); newSession.setProcessRunning(true); return newSession; } catch (IOException e) { sendError("进程启动失败: " + e.getMessage(), userId); return null; } }); // 执行命令 try { logWriter.write("[<<Input<<] " + command + "\n"); assert session != null; session.getCommandWriter().write(command + "\n"); session.getCommandWriter().flush(); } catch (IOException e) { sendError("命令发送失败: " + e.getMessage(), userId); } } catch (IOException e) { e.printStackTrace(); } } // 独立的输出线程方法 private void startOutputThread(UserSession session, String userId, String logFilePath) { new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(session.getCmdProcess().getInputStream(), "gbk"))) { String line; while ((line = reader.readLine()) != null) { // 关键修改:每次输出使用当次会话的userId messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, line); appendToLog(logFilePath, line); // 日志记录方法 } } catch (Exception e) { sendError("输出流异常: " + e.getMessage(), userId); } finally { session.setProcessRunning(false); userSessions.remove(userId); // 清理会话 } }).start(); } public void handleAbort(String userId) { UserSession session = userSessions.get(userId); if (session != null && session.isProcessRunning()) { session.getCmdProcess().destroyForcibly(); sendError("⏹ 用户命令已终止", userId); } else { sendError("⏹ 无执行中命令", userId); } } private void appendToLog(String logFilePath, String content) { try { // 使用加锁写入确保线程安全 synchronized (this) { try (BufferedWriter logWriter = new BufferedWriter( new FileWriter(logFilePath, true))) { logWriter.write(content + "\n"); } } } catch (IOException e) { log.error("日志记录失败: {}", e); // 不中断流程,仅记录错误 } } private void sendError(String message, String userId) { try { // messagingTemplate.convertAndSend("/topic/commandOutput", "❌ " + message); messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "❌ " + message); } catch (Exception ignored) { } } // @PreDestroy // public void cleanup() { // try { // if (cmdProcess != null) { // cmdProcess.destroy(); // isProcessRunning = false; // 允许下次启动时重建进程 // } // } catch (Exception e) { // System.err.println("清理资源时出错: " + e.getMessage()); // } // } } 为什么我输入dir后,日志格式跟我想的不一样?没有 // 记录开始时间 logWriter.write("\n———————— 执行时间 " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " ————————\n");这部分,结果是下面这样的E:\huanan_win\cmd_demo\by_remote_service>dir 驱动器 E 中的卷是 文档 卷的序列号是 5F45-660F E:\huanan_win\cmd_demo\by_remote_service 的目录 2025/06/06 14:58 <DIR> . 2025/06/06 14:58 <DIR> .. 2025/04/17 17:26 395 .gitignore 2025/06/06 15:09 <DIR> .idea 2025/06/06 10:18 11,523 by_remote_service.iml 2025/06/06 14:58 <DIR> command-log 2025/04/17 17:26 1,074 HELP.md 2025/05/28 14:20 4,857 pom.xml 2025/05/28 09:27 <DIR> src 2025/06/06 14:56 <DIR> target 4 个文件 17,849 字节 6 个目录 184,102,072,320 可用字节
最新发布
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值