并发编程中的锁 ゝ一纸荒年。 2024-03-25 20:48 43阅读 0赞 #### 目录 #### * 悲观锁与乐观锁 * 公平锁与非公平锁 * 独占锁与共享锁 * 可重入锁与不可重入锁 * 可中断锁与不可中断锁 * 读/写锁 * 自旋锁 ## 悲观锁与乐观锁 ## **悲观锁顾名思义就是持有悲观态度,线程每次进入临界区处理数据时,都认为数据很容易被其他线程修改**。 所以,在线程进入临界区前,会用户锁锁住临界区的资源,并在处理数据的过程中一直保持锁定状态。其他线程由于无法获取到对应的资源,就会阻塞等待,直到获取锁的线程释放锁,等待的线程才能获取到锁。 Java 中的 synchronized 重量级锁就是一种典型的悲观锁。 **乐观锁顾名思义就是持有乐观的态度,认为每次访问数据的时候其他线程都不会修改数据。** 所以,在访问数据的时候,不会对数据进行加锁操作。当涉及对数据更新的操作时,会检测数据是否被其他线程修改过:如果数据没有被修改过,则当前线程提交更新操作;如果数据被其他线程修改过,则当前线程会尝试再次读取数据,检测数据是否被其他线程修改过,如果再次检测的结果仍然为数据已经被其他线程修改过,则会再次尝试读取数据,如此反复,直到检测到的数据没有被其他线程修改过。 乐观锁在具体实现时,一般会采用版本号机制,先读取数据的版本号,在写数据时比较版本号是否一致,如果版本号一致则更新数据,否则再次读取版本号,比较版本号是否一致,直到版本号一致时更新数据。 Java 中的乐观锁一般都是基于 CAS 自旋实现的。在 Java 中,CAS 是一种原子操作,底层调用的是硬件层面的比较并交换的逻辑。在实现时,会比较当前值与传入的期望值是否相同,如果相同,则把当前值修改为目标值,否则不修改。 Java 中的 synchronized 轻量级锁属于乐观锁,是基于抽象队列同步器(AQS)实现的锁,如 ReentrantLock 等。 > 临界区:临界区一般表示能够被多个线程共享的资源或数据,但是每次只能提供给一个线程使用。临界区资源一旦被占用,其他线程就必须等待。 > 在并发编程中,临界区一般指受保护的对象或者程序代码片段,可以通过加锁的方式保证每次只有一个线程进入临界区,从而达到保护临界区的目的。 ## 公平锁与非公平锁 ## **公平锁的核心思想就是公平,能够保证各个线程按照顺序获取锁,也就是“先来先获取”的原则。** 例如,存在三个线程,分别为线程 1 、线程 2 和线程 3 ,并依次获取锁。首先,线程 1 获取锁,线程 2 和线程 3 阻塞等待,线程 1 执行完任务释放锁。然后,线程 2 唤醒并获取锁,执行完任务释放锁。最后,线程 3 被唤醒并获取锁,执行完任务释放锁。这就是公平锁的流程。 **非公平锁的核心思想就是每个线程获取锁的机会是不平等的,也是不公平的。先抢占锁的线程不一定能够先获取锁。** 例如,存在三个线程,分别为线程 1 、线程 2 和线程 3 ,在线程 1 和线程 2 抢占锁的过程中,线程 1 获取到锁,线程 2 阻塞等待。线程 1 执行完任务释放锁后,在唤醒线程 2 时,线程 3 尝试抢占锁,则线程 3 是可以获取到锁的。这就是非公平锁。 在 Java 中,ReentrantLock 默认的实现为非公平锁,也可以在构造方法中传入 true 来创建公平锁对象。 ## 独占锁与共享锁 ## **独占锁也叫排他锁,在多个线程争抢锁的过程中,无论是读操作还是写操纵,只能有一个线程获取到锁,其他线程阻塞等待,独占锁采取的是悲观保守策略。** 独占锁的缺点是无论对于读操作还是写操作,都只能有一个线程获取锁。但是读操作不会修改数据,如果当读操作线程获取锁时其他的读线程被阻塞,就会大大降低系统的读性能。此时,就需要用到共享锁了。 **共享锁允许多个读线程同时获取临界区资源,它采取的是乐观锁的机制。共享锁会限制写操作与写操作之间的竞争,也会限制写操作与读操作之间的竞争,但是不会限制读操作与读操作之间的竞争。** 在 Java 中,ReentrantLock 是一种独占锁,而 ReentrantReadWriteLock 可以实现读/写锁的分离,允许多个读操作同时获取读锁。 ## 可重入锁与不可重入锁 ## **可重入锁也叫递归锁,指同一个线程可以多次占用同一个锁,但是在解锁时,需要执行相同次数的解锁操作。** 例如,线程 A 在执行任务的过程中获取锁,在后续执行任务的过程中,如果遇到抢占同一个锁的情况,则也会再次获取锁。 **不可重入锁与可重入锁在逻辑上是相反的,指一个线程不能多次占用同一个锁。** 例如,线程 A 在执行任务的过程中获取锁,在后续执行任务的过程中,如果遇到抢占同一个锁的情况,则不能再次获取锁。只有先释放锁,才能再次获取该锁。 在 Java 中,ReentrantLock 就是一种可重入锁。 ## 可中断锁与不可中断锁 ## 可中断锁与不可中断锁主要指线程在阻塞等待的过程中,能否中断自己阻塞等待的状态。 **可中断锁指锁被其他线程获取后,某个线程在阻塞等待的过程中,可能由于等待的时间过长而中断阻塞等待的状态,去执行其他任务。** **不可中断锁指锁被其他线程获取后,某个线程如果也想获取这个锁,就只能阻塞等待。如果占用锁的线程一致不释放锁,其他想获取锁的线程就会一直阻塞等待。** 在 Java 中,ReentrantLock 是一种可中断锁,synchronized 则是一种不可中断锁。 ## 读/写锁 ## **读/写锁分别为读写和写锁,当持有读锁时,能够对共享资源进行读操作,当持有写锁的时,能够对共享资源进行写操作**。写操作具有排他性,读锁具有共享性。在同一时刻,一个读/写锁只允许一个线程进行写操作,可以允许多个线程进行读操作。 当某个线程试图获取写锁时,如果发现其他线程已经获取到写锁或者读锁,则当前线程会阻塞等待,直到任何线程不再持有写锁或者读锁。 当某个线程试图获取读锁时,如果发现其他线程已经获取到读锁,则这个线程会直接获取到读锁。 当某个线程试图获取读锁时,如果发现其他线程已经获取到写锁,则这个线程会阻塞等待,直到占有写锁的线程释放锁。 在 Java 中,ReadWriteLock 是一种读/写锁。 ## 自旋锁 ## **自旋锁指某个线程在没有获取到锁时,不会立即进入阻塞等待状态,而是不断尝试获取锁,直到占有锁的线程释放锁。** 自旋锁可能引起死锁和占用 CPU 时间过长的问题。 程序不能在占用自旋锁时调用自己,也不能在递归调用时获取相同的自旋锁,可以在一定程度上避免死锁。 当某个线程进入不断尝试获取锁的循环时,可以设定一个循环时间或循环次数,超过这个时间或者次数,就让线程进入阻塞等待的状态,在一定程度上可以有效的避免长时间占用 CPU 的问腿。 在 Java 中,CAS 是在一种自旋锁。
相关 并发编程:Java中的死锁问题 在Java的多线程环境中,死锁是一种常见的并发问题。死锁发生时,系统无法进行正常的资源分配,因为每个线程都在等待其他线程释放资源。 以下是一个简单的Java代码示例,展示了如 太过爱你忘了你带给我的痛/ 2024年09月16日 04:57/ 0 赞/ 22 阅读
相关 Java并发编程中的锁问题实例 在Java并发编程中,锁(Lock)是一种同步机制,用于控制多个线程对共享资源的访问。以下是一个常见的锁问题实例: **场景:生产订单** - **共享资源:订单列表(Arr ﹏ヽ暗。殇╰゛Y/ 2024年09月12日 12:30/ 0 赞/ 18 阅读
相关 Java并发编程中锁的使用实例 在Java并发编程中,锁是一种同步机制,用于保护共享资源(如变量、集合等)免受多个线程同时访问带来的问题。 以下是一个简单的锁使用实例,我们有一个共享的计数器: ```ja 怼烎@/ 2024年09月12日 11:36/ 0 赞/ 11 阅读
相关 Java并发编程中的锁升级问题 在Java的并发编程中,锁升级(Lock Involvement)是一个需要注意的问题。通常在多线程环境中,有以下几种类型的锁: 1. `synchronized`块/方法: Love The Way You Lie/ 2024年09月11日 21:36/ 0 赞/ 20 阅读
相关 并发编程:Java中的死锁示例 在Java编程中,死锁是一个常见的并发问题。当两个或更多的线程互相等待对方释放资源时,就会发生死锁。 以下是一个Java中创建死锁的简单示例: ```java import 左手的ㄟ右手/ 2024年09月10日 04:03/ 0 赞/ 31 阅读
相关 并发编程中的锁 目录 悲观锁与乐观锁 公平锁与非公平锁 独占锁与共享锁 可重入锁与不可重入锁 可中断锁与不可中断锁 读/写锁 自旋锁 悲观锁 ゝ一纸荒年。/ 2024年03月25日 20:48/ 0 赞/ 44 阅读
相关 (六)并发编程之Java中的锁 公平锁和非公平锁 公平锁 是指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到 非公平锁 是指在多线程获取锁的顺序并不是按照申请 Dear 丶/ 2022年01月26日 02:17/ 0 赞/ 258 阅读
相关 Java并发编程(六)Java中的锁 一、Lock接口 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源, 港控/mmm°/ 2021年09月26日 23:44/ 0 赞/ 355 阅读
还没有评论,来说两句吧...