JAVA实现对文件加锁防篡改 --《JAVA编程思想》83

在代码中,可以通过 synchronized 关键字对代码片段进行加锁,假设我们需要对文件进行加锁,synchronized 只能对 JAVA 执行代码进行加锁,倘若另外一个操作文件的线程是操作系统中其他的某个本地线程呢?这时仅仅通过 synchronized 关键字来加锁显然是不行的。

但好在 JDK 1.4 引入了针对本地操作系统的文件加锁机制,下面便和大家一起来学习。

    public static void main(String[] args) throws IOException, InterruptedException {
        //获取文件,第二个参数为是否追加写,不设置为默认覆盖整个文件
        FileOutputStream fos = new FileOutputStream("D:\\test\\bigFile.txt", true);
        //获取文件通道
        FileChannel fc = fos.getChannel();
        //尝试对文件加锁
        FileLock fl = fc.tryLock();
        // FileLock不等于null则加锁成功
        if (fl != null) {
            System.out.println("Locked File");
            //指定线程睡眠10秒
            TimeUnit.SECONDS.sleep(10);
            //释放锁
            fl.release();
            System.out.println("Released Lock");
        }
        //关闭流
        fos.close();
    }
Locked File
Released Lock

getChannel() 获取通道 FileChannel ,它可以调用 tryLock() 和 lock() 对文件进行加锁。

tryLock() 是非阻塞式的,它设法获取锁,如果获取不到(其他线程已拿到锁,并且不共享锁时),则会直接返回。

lock() 是阻塞式的,它会一直阻塞直到可以获得锁,或者调用 lock() 的线程中断、调用 lock() 的通道关闭。

release() 会释放锁。

我们也可以对文件的一部分加锁:

 tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)

第一个参数为起始位置,第二个参数为结束位置,第三个参数为是否为共享锁。

值得一提的是,无参数的加锁方法会对整个文件进行加锁,加锁区域会随着文件的尺寸而进行变化;固定尺寸的锁不随文件尺寸做出变化,即超出 size - position 之外的区域是不会被锁定的。

操作系统必须支持共享锁才可以进行使用,可以通过 isShared() 来查询。

最后,演示一个通过映射文件的部分进行加锁的例子,不同的线程对文件的不同部分进行加锁,两者可以同时修改,像我们的数据库就是基于这种原理。

public class LockingMappedFiles {

    //128MB
    private static final int LENGTH = 0X8FFFFFF;

    private static FileChannel fc;

    private static class LockAndModify extends Thread {

        private ByteBuffer buffer;

        private int start, end;

        public LockAndModify(ByteBuffer mbb, int start, int end) {
            this.start = start;
            this.end = end;
            mbb.limit(end);
            mbb.position(start);
            //截取position至limit之间的区域作为新缓冲区
            buffer = mbb.slice();
            start();
        }

        @Override
        public void run() {
            try {
                FileLock fl = fc.lock(start, end, false);
                System.out.println("Locked:" + start + " to " + end);
                while (buffer.position() < buffer.limit() - 1) {
                    //将x的ASCII码+1变成y
                    buffer.put((byte) (buffer.get() + 1));
                }
                fl.release();
                System.out.println("Released:" + start + " to " + end);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        fc = new RandomAccessFile("D:\\test\\bigFile.txt", "rw").getChannel();
        MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
        for (int i = 0; i < LENGTH; i++) {
            out.put((byte) 'x');
        }
        new LockAndModify(out, 0, 0 + LENGTH / 3);
        new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
    }

}

```java
Locked:0 to 50331647
Locked:75497471 to 113246206
Released:75497471 to 113246206
Released:0 to 50331647

本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。

若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!

### 乐观锁与悲观锁的概念及区别 #### 概念定义 - **悲观锁**是指每次访问数据时都认为其他用户可能对其进行修改,因此在获取数据前会先加锁。只有持有锁的事务才能对该数据进行操作,其余事务需等待锁释放后才能继续执行[^2]。 - **乐观锁**则假设并发冲突发生的概率较低,通常不会对数据加锁。它通过版本号或其他逻辑方式验证数据的一致性,在提交更新时检查是否有其他事务已修改该数据。如果发现冲突,则拒绝当前事务并返回错误[^3]。 --- #### 主要区别 | 对比维度 | 悲观锁 | 乐观锁 | |----------------|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| | **核心思想** | 认为并发冲突不可避免,必须提前锁定资源以防被他人篡改 | 认为并发冲突较少发生,无需提前锁定资源 | | **实现方式** | 利用数据库自带的锁机制(如行级锁、表级锁),或者编程语言中的同步工具 | 使用版本号或时间戳字段检测数据变更情况 | | **性能影响** | 锁定期间阻止其他事务访问同一资源,可能导致高并发下的性能瓶颈 | 不涉及物理锁,减少了阻塞现象,但在高频冲突下可能出现大量失败重试 | | **适用场景** | 更新密集型业务,例如银行账户转账等需要强一致性保障的操作 | 查询为主、更新频率低的场景,例如商品浏览统计 | --- #### 实现原理 ##### 悲观锁 悲观锁主要依靠数据库自身的锁机制完成。常见的做法是在 SQL 中显式指定 `SELECT ... FOR UPDATE` 或者 `LOCK IN SHARE MODE` 来锁定目标记录。例如: ```sql -- 加排他锁 (FOR UPDATE),防止其他事务修改此条记录 SELECT * FROM products WHERE id = 1 FOR UPDATE; ``` 上述语句会在选定的行上施加重入锁,确保后续操作独占这些行直至事务结束[^4]。 ##### 乐观锁 乐观锁一般基于版本控制策略实施。具体而言,每张表新增一个整数类型的 `version` 字段用于追踪更改次数。当某笔交易尝试保存改动时,系统会对比原始版本值是否匹配最新状态。如果不符说明已被第三方抢先一步调整过,则抛出异常终止流程;反之则顺利推进并将计数值累增一位作为新基准供下次校验参照之用。下面给出一段 Java 示例代码展示如何运用 Hibernate 支持的 @Version 注解达成这一目的: ```java @Entity public class Product { private Long id; @Version private Integer version; // 自动维护版本号 private String name; // getters and setters... } // 调用示例 Product product = entityManager.find(Product.class, productId); product.setName("New Name"); entityManager.merge(product); // 如果版本不符将触发 OptimisticLockException ``` 此处的关键在于框架内部实现了自动化的版本比较功能,从而简化开发者手动编写复杂逻辑的工作负担[^5]。 --- ### 总结 无论是采用哪种模式都需要权衡利弊选取最适合特定需求的技术手段加以应用。对于那些实时性强且竞争激烈的任务来说推荐优先考虑悲观方案以换取更高的安全性;而对于读多于写的环境则可以大胆选用更加灵活高效的乐观途径来提升整体吞吐能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BaymaxCS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值