【Java原理系列】 Java lock 关键字的原理用法详解

Java lock 关键字的原理用法详解

原理

在 Java 中,lock 是一个接口 java.util.concurrent.locks.Lock 的实现。与 synchronized 关键字相比,lock 提供了更加灵活的同步机制。

lock 接口提供了两个主要方法:lock()unlock()。当一个线程调用 lock() 方法时,它会尝试获取锁,如果锁已经被其他线程持有,则该线程将被阻塞,直到获取到锁为止。而当一个线程调用 unlock() 方法时,它会释放锁,以便其他线程可以获取锁并执行。

lock 的原理是通过显示地获取和释放锁来实现线程之间的同步。lock 可以实现更细粒度的锁定,允许线程按照自己的需要来获取和释放锁,从而提高程序的并发性能。

分类

lock属于显式锁(Explicit Lock),即使用 java.util.concurrent.locks.Lock 接口及其实现类提供的锁机制。显式锁需要手动获取和释放,提供了更灵活的控制能力。常见的实现类有 ReentrantLockReadWriteLock 等。

ReentrantLockReadWriteLock 都是 Java 中用于实现线程同步的锁机制,但它们在功能和应用场景上有所不同。

ReentrantLock

ReentrantLock 是一个可重入锁,也是 Lock 接口的实现类。它提供了与 synchronized 关键字类似的线程同步功能,但具有更多的灵活性和高级特性。

ReentrantLock 具有以下特点:

  • 可重入性:同一个线程可以多次获取同一个 ReentrantLock 而不会被阻塞,这样可以避免死锁的发生。
  • 公平性选择:可以选择公平锁(Fair Lock)或非公平锁(Nonfair Lock)。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许插队,可能导致某些线程长时间等待。
  • 锁的中断支持:ReentrantLock 提供了对锁的中断支持,即当一个线程等待锁时,可以通过调用 lockInterruptibly() 方法使其可被中断。
  • 条件变量:ReentrantLock 提供了 Condition 接口及其实现类 ConditionObject,用于实现更复杂的线程通信和等待/唤醒机制。

使用示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Example {
    private Lock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            // 执行需要同步的代码块
        } finally {
            lock.unlock();
        }
    }
}

ReadWriteLock

ReadWriteLock 是一个读写锁,它允许多个线程同时读取共享资源,但在写操作时只能有一个线程进行,以保证数据一致性和并发性能。

ReadWriteLock 包含两个关键接口:

  • ReadLock:用于获取读锁。多个线程可以同时持有读锁,只要没有线程持有写锁。
  • WriteLock:用于获取写锁。只有当没有线程持有读锁或写锁时,才能成功获取写锁。

ReadWriteLock 具有以下特点:

  • 读写分离:允许多个线程同时读取共享资源,提高并发性能。
  • 写操作互斥:在写操作时,只能有一个线程持有写锁,以确保数据一致性。
  • 降级支持:允许从写锁降级为读锁,即在获取写锁之后再获取读锁,以避免阻塞其他线程的读操作。

使用示例:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Example {
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void readData() {
        lock.readLock().lock();
        try {
            // 执行读取共享资源的操作
        } finally {
            lock.readLock().unlock();
        }
    }

    public void writeData() {
        lock.writeLock().lock();
        try {
            // 执行写入共享资源的操作
        } finally {
            lock.writeLock().unlock();
        }
    }
}

注意事项

在使用 lock 关键字时,需要注意以下几点:

  • 在使用 lock 锁时,要确保在获取锁之后,一定要释放锁,否则会导致死锁的发生。
  • 通常情况下,在 finally 块中释放锁是一个好的实践,以确保无论是否发生异常,都能正确地释放锁。
  • lock 提供了更加灵活的同步机制,可以实现更细粒度的锁定,但同时也增加了代码的复杂性,需要仔细处理锁的获取和释放逻辑,避免出现潜在的问题。
  • 当使用 lock 锁时,要确保所有访问共享资源的方法都使用同一个 lock 对象进行同步,否则仍然可能出现线程安全问题。
  • lock 可以配合条件变量等高级特性一起使用,提供更强大的同步控制能力。
  • 在使用 ReentrantLockReadWriteLock 时,需要确保在适当的位置调用 unlock() 方法来释放锁,以避免死锁的发生。
  • 对于 ReadWriteLock,要根据实际需求选择合适的读写策略,避免频繁地进行锁的切换

乐观锁悲观锁延伸

乐观锁和悲观锁是并发控制中常见的两种策略。

乐观锁

  • 乐观锁假设在大多数情况下,读操作不会与其他操作发生冲突。
  • 在乐观锁中,线程在读取数据时不会加锁,而是在更新数据之前检查是否有其他线程对该数据进行了修改。
  • 如果检测到其他线程已经修改了数据,则当前线程可以进行相应的冲突处理,如重试操作或放弃更新。
  • 常见的乐观锁实现方式包括使用版本号、时间戳等机制来标识数据的状态和变化。

使用实例:

import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample {
    private AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        int oldValue;
        int newValue;
        do {
            // 读取当前值
            oldValue = counter.get();

            // 计算新值
            newValue = oldValue + 1;
        } while (!counter.compareAndSet(oldValue, newValue));
    }
}

在上述示例中,使用 AtomicInteger 类来实现乐观锁。AtomicInteger 提供了原子性的读取和更新操作,并通过 compareAndSet 方法进行比较并设置新值。如果在比较和设置过程中发现有其他线程修改了值,则重试。

悲观锁

  • 悲观锁假设在并发环境中,读操作和写操作之间存在冲突。
  • 在悲观锁中,线程在访问数据之前会先获取锁,确保在任何时候只有一个线程可以访问共享资源。
  • 当一个线程获得了悲观锁后,其他线程需要等待锁的释放才能访问共享资源。
  • 常见的悲观锁实现方式包括使用 synchronized 关键字、ReentrantLock 类等来实现锁机制。

乐观锁适用于读操作较多、冲突发生的概率较低的场景,它减少了锁的开销,提高了并发性能。悲观锁适用于冲突频繁发生的场景,通过加锁来保证数据的一致性和安全性。

使用实例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PessimisticLockExample {
    private Lock lock = new ReentrantLock();
    private int counter = 0;

    public void increment() {
        lock.lock();
        try {
            // 执行更新操作
            counter++;
        } finally {
            lock.unlock();
        }
    }
}

在上述示例中,使用 ReentrantLock 实现悲观锁。在 increment 方法中,首先获取锁,在执行更新操作后释放锁。这样可以确保在任何时候只有一个线程能够访问共享资源,保证数据的一致性和安全性。

需要注意的是,在使用悲观锁时,一定要确保在适当的位置释放锁,以避免死锁或资源泄露的情况发生。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BigDataMLApplication

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

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

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

打赏作者

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

抵扣说明:

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

余额充值