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

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

时间:2024-09-03 22:52:30浏览次数:7  
标签:下篇 AQS 间隙 -- lock ReentrantLock 线程 2.1 多线程

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语句将该范围内的索引值及其后的间隙锁定,确保其他事务不会在此范围内插入、删除或修改数据。

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

标签:下篇,AQS,间隙,--,lock,ReentrantLock,线程,2.1,多线程
From: https://blog.csdn.net/weixin_61769871/article/details/141872426

相关文章

  • 多线程、任务、异步的区别
    Task和Thread的区别这是一个高频,深刻的问题,无论去哪都逃不过被询问这个问题。Task是基于Thread的,这是众所周知的。但是Task和Thread的联系如此简单和纯粹确实我没想到的。甚至只需要几十行代码就能呈现其原理。一个简单的模拟实例说明Task及其调度问题,这真是一篇好文章。任务体......
  • 大二必做项目贪吃蛇超详解之下篇游戏核心逻辑实现
    贪吃蛇系列文章上篇win32库介绍中篇设计与分析下篇游戏主逻辑可以在Gitee上获取贪吃蛇代码。文章目录贪吃蛇系列文章5.核心逻辑实现分析5.3GameRun5.3.1PrintScore5.3.2CheckVK5.3.3BuyNewNode5.3.4NextIsFood5.3.4EatFood5.3.5NotFood5.3.6C......
  • 【多线程】 - 实现方法以及自定义线程池
    概念进程进程是程序的基本执行实体线程线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。多个线程组成了多线程多线程应用场景软件中的耗时操作拷贝、迁移大文件加载大量的资源文件想让多个事情同时运行就需要多线程并发和并行并发在同一时刻......
  • 二、并发编程与多线程-2.1、J.U.C和锁(中篇)
    2.1、J.U.C和锁(中篇)2.1.4、什么是CAS?答:CAS是Java中Unsafe类里面的方法,全称是CompareAndSwap,是比较并交换的意思。作用就是保证在多线程环境下,对于修改共享变量操作的原子性。扩展:CAS保证修改共享变量操作原子性的实现逻辑:CAS方法里有三个参数,依次分别是共享变量的内......
  • flask多线程下数据库操作(简单示例)
    前言背景:开了两个线程操作数据库插入但是获取不到db的信息,自己摸索的方法不一定是最佳的,有更好的可以评论或私信,感谢大佬话不多说,直接上代码 #模型里面的多线程新增操作@staticmethoddefadd_users_by_thread(username,password,session):user=U......
  • 为什么多线程会带来性能问题?
    为什么多线程会带来性能问题?什么是性能问题在上一篇中,我们已经学习了多线程带来的线程安全问题,但对于多线程而言,它不仅可能会带来线程安全问题,还有可能会带来性能问题,也许你会奇怪,我们使用多线程的最大目的不就是为了提高性能吗?让多个线程同时工作,加快程序运行速度,为什么反而会带来......
  • [java][代码]Java中创建多线程方法
     在Java中,创建多线程有多种方法。以下是一些常见的方法:1.继承Thread类通过继承Thread类并重写其run方法来创建线程。classMyThreadextendsThread{publicvoidrun(){//线程要执行的任务System.out.println("线程运行中...");}......
  • 多线程的使用-->3
    1.死锁在线程同步过程中,因为多线程争抢锁资源,所以有些线程会执行,有些线程会等待。如果线程A和线程B分别需要X和Y两个锁资源恰好A获得了X资源,准备争抢Y,而B获得了Y资源,准备争抢X,此时A和B就进入了一中死锁状态。如何解决死锁问题?①从业务逻辑层面解决让它们随机抢资源......
  • 在多线程环境下,如何解决 Java 函数失效的问题?,java 多线程处理数据
    在多线程环境下,Ja函数失效的问题是开发者常常遇到的一个挑战。多线程带来了并发执行的优势,但同时也增加了代码的复杂性,尤其是在涉及共享资源时,如果处理不当,可能会导致函数失效,甚至引发更严重的问题。本文将探讨一些常见的Ja函数失效原因,并提供相应的解决方法。我们需要了解函数失......
  • 多线程篇(ThreadLocal & 内存模型 & 伪共享(ThreadLocal ))(持续更新迭代)
    目录一、ThreadLocal1.前言2.简介3.与Synchronized的区别4.简单使用5.原理5.1.set5.2.get5.3.remove5.4.与Thread,ThreadLocalMap之间的关系5.常见使用场景场景一:存储用户Session场景二、数据库连接,处理数据库事务场景三、数据跨层传递(controller,servi......