二、并发编程与多线程-2.1、J.U.C和锁(下篇)

2.1、J.U.C和锁(下篇)

2.1.8、什么是可重入锁?它的作用是什么?

答:
在Java中,可重入锁是一种同步机制,它允许同一个线程多次获取同一个锁。当一个线程持有该锁时,它可以反复进入被该锁保护的代码块,而不会被阻塞。这种机制也被称为递归锁。比如synchronized锁和ReentrantLock锁都是可重入锁。

可重入锁的作用是解决线程在执行代码块时对共享资源的并发访问问题。当一个线程获取了锁之后,其他线程就不能获取这个锁,只能等待该线程释放锁。可重入锁确保了同一线程在持有锁的同时可以再次获取锁,防止了死锁的发生

扩展:
除了基本的加锁和释放锁的功能,可重入锁还提供了更多的灵活性和功能,例如可设置超时时间、可实现公平锁和非公平锁、可实现条件变量等。这些特性使得可重入锁在复杂的多线程环境下更加灵活和强大,能够满足不同的线程同步需求。
实际应用就是说,如果要确保一个方法在多线程环境下是安全的,给这个方法加一个锁就行了;如果业务逻辑是这个方法或代码块的内部还调用了其他方法,那就把这个方法和这个方法调用到的方法都加上同一个锁,就确保了在多线程环境下的线程安全,其实就是利用了属于可重入锁的可重入的特性。

示例:
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                lock.lock();
                System.out.println("线程1获取到了锁");
                // 调用同一个锁的方法
                method1();
            } finally {
                lock.unlock();
                System.out.println("线程1释放了锁");
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                lock.lock();
                System.out.println("线程2获取到了锁");
                // 调用同一个锁的方法
                method2();
            } finally {
                lock.unlock();
                System.out.println("线程2释放了锁");
            }
        });

        thread1.start();
        thread2.start();
    }

    public static void method1() {
        try {
            lock.lock();
            System.out.println("进入method1方法");
        } finally {
            lock.unlock();
            System.out.println("method1方法执行完毕并释放锁");
        }
    }

    public static void method2() {
        try {
            lock.lock();
            System.out.println("进入method2方法");
        } finally {
            lock.unlock();
            System.out.println("method2方法执行完毕并释放锁");
        }
    }
}
在这个例子中,我们创建了一个可重入锁 lock,然后在两个线程中使用该锁进行同步操作。线程1首先获取了锁,并进入了 method1 方法,然后又获取了同样的锁,并进入了 method2 方法。线程2也获取了同一个锁,并依次进入了 method1 和 method2 方法。最后,两个线程都释放了锁。
由于可重入锁的特性,线程在持有锁的同时可以再次获取同一个锁,不会被阻塞。所以在这个例子中,线程在调用 method1 和 method2 方法时都能够顺利执行,不会发生死锁。
运行结果:
线程1获取到了锁
进入method1方法
method1方法执行完毕并释放锁
线程1释放了锁
线程2获取到了锁
进入method2方法
method2方法执行完毕并释放锁
线程2释放了锁

2.1.9、ReentrantLock的实现原理是什么?

答:
ReentrantLock是Java中提供的一个可重入锁实现类。它实现了Lock接口,并提供了与synchronized关键字类似的线程同步机制。ReentrantLock的实现原理主要涉及以下几个方面:

  1. AQS(AbstractQueuedSynchronizer):ReentrantLock的核心实现是基于AQS框架。AQS是一个同步器框架,提供了基于FIFO队列的等待线程管理功能。ReentrantLock通过继承AQS,并重写其方法来实现具体的功能。
  2. CAS(Compare and Swap):ReentrantLock内部使用CAS操作实现对锁状态的原子更新。CAS操作是一种无锁的线程同步机制,利用硬件原语提供的原子性操作,可以在没有锁的情况下实现线程间的协调与同步。
  3. Condition条件变量:ReentrantLock还提供了Condition接口实现类ConditionObject作为条件变量,用于实现线程间的等待和唤醒机制。Condition对象提供了类似于synchronized关键字中的wait和notify的功能,但使用方式更加灵活。
  4. 公平锁与非公平锁:ReentrantLock可以配置为公平锁和非公平锁。公平锁表示线程按照申请锁的顺序获得锁,而非公平锁则不保证线程获取锁的顺序。这个选择可以通过构造函数或者Lock接口的方法进行配置。

扩展:
锁的竞争,ReentrantLock是通过互斥变量,使用CAS机制来实现的。没有竞争到锁的线程,会被AQS这样一个队列同步器来存储,底层是通过双向链表实现的。当锁被释放以后,会从AQS队列的头部唤醒下一个等待锁的线程。

2.1.10、ReentrantLock是如何实现锁的公平性和非公平性的?

答:
公平:指的是竞争锁资源的线程,严格按照请求顺序来分配锁。
非公平:指的是竞争锁资源的线程,允许插队来抢占资源。
ReentrantLock内部使用了AQS来实现锁资源的竞争,没有竞争到锁资源的线程,会加入到AQS同步队列中,这个队列是一个FIFO的双向链表。
在这样的背景下,公平锁的实现方式是,线程在竞争锁资源的时候先判断AQS同步队列中有没有等待的线程,如果有,就加入到队列的尾部等待。而非公平锁的实现方式是,不管AQS同步队列中有没有线程等待,竞争锁资源的线程都会先尝试抢占锁资源,如果抢不到,再加入到AQS同步队列中进行等待

扩展:
ReentrantLock和synchronized默认采用的都是非公平锁的策略,之所以这么设计,就是为了提升线程竞争锁资源的性能。因为使用非公平策略,当前线程正好在上一个线程释放锁的临界点抢到了锁,就意味着这个线程不需要切换到内核态。

2.1.11、说说你对行锁、间隙锁、临键锁的理解

答:
行锁、间隙锁和临键锁是数据库中用于并发控制的锁机制。它们具有以下含义:

  1. 行锁(Row lock):行级锁是对数据库中的单个数据行进行锁定,以防止其他事务对其进行修改或读取。行级锁可以减少事务之间的冲突,提高并发访问性能。行锁可以被其他事务申请或等待,直到锁被释放。

  2. 间隙锁(Gap lock):间隙锁是指在索引范围内的间隙上设置的锁。它锁定了两个索引值之间的空隙,以防止其他事务在此范围内插入新数据。间隙锁的目的是防止幻读,即当一个事务读取间隙中不存在的数据时,如果其他事务在此间隙插入了数据,则会产生幻像读取的情况。

  3. 临键锁(Next-Key lock):临键锁是行锁和间隙锁的结合。它锁定了索引值和其后的间隙,以确保其他事务不会在此范围内插入、删除或修改数据。临键锁是为了解决幻读问题而引入的,它可以防止其他事务在范围内插入或删除数据,从而提供一致的读取结果。

以下是基于MySQL数据库的示例代码,展示了如何使用行锁、间隙锁和临键锁。

  1. 行锁示例代码:
-- 开启事务
START TRANSACTION;

-- 锁定某一行
SELECT * FROM table_name WHERE id = 1 FOR UPDATE;

-- 执行其他操作

-- 提交事务
COMMIT;

在这个示例中,通过FOR UPDATE语句将id为1的行锁定,避免其他事务对该行进行修改或读取,直到事务提交。

  1. 间隙锁示例代码:
-- 开启事务
START TRANSACTION;

-- 锁定索引范围内的间隙
SELECT * FROM table_name WHERE id > 10 AND id < 20 FOR UPDATE;

-- 执行其他操作

-- 提交事务
COMMIT;

在这个示例中,通过指定id值的范围,FOR UPDATE语句将该范围内的间隙锁定,防止其他事务在此范围内插入新数据。

  1. 临键锁示例代码:
-- 开启事务
START TRANSACTION;

-- 锁定索引值及其后的间隙
SELECT * FROM table_name WHERE id >= 10 AND id < 20 FOR UPDATE;

-- 执行其他操作

-- 提交事务
COMMIT;

在这个示例中,通过指定id值的范围,FOR UPDATE语句将该范围内的索引值及其后的间隙锁定,确保其他事务不会在此范围内插入、删除或修改数据。

这些锁机制主要用于保证数据库的数据一致性和并发性。行锁用于保护单个数据行的修改和读取操作,间隙锁用于防止幻读,临键锁综合了行锁和间隙锁的特性,提供了更全面的并发控制。在实际应用中,需要根据具体的业务需求和并发访问情况来选择合适的锁机制。

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值