并发编程中常见的锁策略

目录

[一. 悲观锁和乐观锁](#一. 悲观锁和乐观锁)

[二. 重量级锁和轻量级锁](#二. 重量级锁和轻量级锁)

[三. 挂起等待锁和自旋锁](#三. 挂起等待锁和自旋锁)

[四. 公平锁和非公平锁](#四. 公平锁和非公平锁)

[五. 可重入锁和不可重入锁](#五. 可重入锁和不可重入锁)

[六. 读写锁](#六. 读写锁)

[七. synchronized](#七. synchronized)

[八. 锁消除](#八. 锁消除)

[九. 锁粗化](#九. 锁粗化)


一. 悲观锁和乐观锁

1. 乐观锁: 乐观锁 在加锁时, 假设出现锁冲突的概率不大 --> 接下来围绕加锁做的工作就更少.

2. 悲观锁: 悲观锁 在加锁时, 假设出现锁冲突的概率很大 --> 接下来围绕加锁做的工作就更多.

[注]: synchronized这个锁是"自适应锁". 它在初始情况下是乐观的, 预估出现锁冲突的概率不大 . 但是会统计锁冲突的次数, 当所冲突的次数达到一定值之后, 就会从乐观锁转变为悲观锁.

二. 重量级锁和轻量级锁

1. 重量级锁: 加锁的开销比较大, 围绕加锁要做的工作更多.

(一般来说, 悲观锁都是重量级的).

2. 轻量级锁: 加锁的开销比较小, 围绕加锁要做的工作更少.

(一般来说, 乐观锁都是轻量级的).

三. 挂起等待锁和自旋锁

1. 挂起等待锁: 挂起等待锁 就是悲观锁(重量级锁)的一种典型实现.

挂起等待锁采用的等待策略是 "挂起等待", 等待过程中释放CPU资源, 这样的话就不用一直占用CPU等待锁资源, 可以让出CPU资源做别的事情了. 如果锁资源释放, 会以某种方式通知该线程.

2. 自旋锁: 自旋锁 就是乐观锁(轻量级锁)的一种典型实现.

自旋锁采用的等待策略是 "忙等", 等待过程中不会释放CPU资源, 等待过程中一直不停地检测锁是否被释放, 如果释放, 就有机会立即获取锁资源.

四. 公平锁和非公平锁

1. 公平锁: 公平锁确保 锁的获取是按照线程请求的顺序进行的 . (即: ++最先请求的线程会最先获取到锁++).

2. 非公平锁: 非公平锁不能保证 锁的获取是按照线程锁请求的顺序进行的 . (++当锁被释放时, 任何线程都有机会获得锁++, 即使是刚开始尝试获取锁的线程).

[注]: synchronized就属于非公平锁, 当synchronized的锁资源释放后, 等待锁资源的n个线程就会重新竞争, 下一个是哪个线程拿到锁是不确定的.

五. 可重入锁和不可重入锁

1. 可重入锁: 可重入锁是指同一个线程可以多次获取同一把锁 . 如果某线程已持有锁, 那么当该线程再次尝试获取该锁资源时 (再次进入由这个锁保护的代码块) , 不会发生阻塞.

(可重入锁可以防止死锁的发生,因为同一个线程可以重复获取已经持有的锁)

2. 不可重入锁: 不可重入锁是指如果某线程已经持有锁, 那么它不能再次获取这个锁 , ++否则就会导致死锁.++

java中也对可重入锁进行了封装. java中用 ReentrantLock 这个类来实现可重入锁.

3. ReentrantLock 和 synchronized的区别

(1) synchronized 是关键字, 而ReentrantLock 是java标准库中的一个类.

(2) synchronized 通过代码块加锁解锁, 而ReentrantLock通过 lock() 和 unlock() 实现加锁解锁.

(3) synchronized 没有 try-lock 这样的锁风格, 当加锁失败的时候, 就会阻塞等待, 等待锁资源释放; 而ReentrantLock提供了 try-lock 这样的锁风格, 当加锁失败的时候, 不会阻塞等待, 而是直接返回一个返回值, 通过返回值来表示加锁成功还是失败.

(3) synchronized 一定是非公平锁, 无法修改; 而 ReentrantLock 默认为非公平锁, 但是可以通过给构造方法传入参数来把 ReentrantLock 设定成公平锁.

(4) synchronized 的等待和唤醒都是通过wait() -- notify() 来完成的; 而ReentrantLock提供了功能更强的"等待--通知"机制, 基于Condition类实现.

六. 读写锁

读写锁是一种用于解决多线程环境中读操作和写操作之间冲突 的锁机制. 读写锁可以提高并发程序的性能, 尤其是在++读操作远多于写操作的场景++中.

1. 分类:

(1) 读锁: 多个线程可以同时持有读锁.

(2) 写锁: 写锁是"排他"的, 即任何时候只能有一个线程持有写锁, 并且在此期间不能有其他线程进行读操作或者写操作.

2. 特点:

(1) 读读共享: 多个线程可以同时获取读锁.

(2) 读写互斥: 读操作和写操作不能同时进行. (如果某线程持有写锁, 那么其他线程无法获取读锁或者写锁)

(3) 写写互斥: 写操作不能同时进行. (如果某线程持有写锁, 那么其他线程不能获取写锁)

3. 实现方式

java中, ReadWriteLock 接口 和++它的实现类++ ReentrantReadWriteLock 提供了++读写锁的功能++.

java 复制代码
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo33 {

    public class ReadWriteLockExample {
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //创建一个读写锁对象
        private int data = 0; // 共享数据

        // 读操作
        public int read() {
            readWriteLock.readLock().lock(); //调用读锁
            try {
                // 执行读操作
                return data;
            } finally {
                readWriteLock.readLock().unlock();
            }
        }

        // 写操作
        public void write(int value) {
            readWriteLock.writeLock().lock(); //调用写锁
            try {
                // 执行写操作
                data = value;
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }
    }

}

上述代码中, read操作获取读锁, 允许多个线程同时获取数据 ; write操作获取写锁, 确保在++写入或修改数据时++ 没有其他线程进行读写操作.

七. synchronized

1. synchronized锁的特点

(1) 悲观乐观 --> 自适应

(2) 重量轻量 --> 自适应

(3) 挂起等待 / 自旋 --> 自适应

(4) 是非公平锁

(5) 是可重入锁

(6) 不是读写锁

2. synchronized的加锁过程

synchronized的加锁过程, 实际上是一个**"锁升级"**的过程.

使用synchronized加锁, 刚开始, synchronized会处于"偏向锁"的状态, 即只是 "做个标记" , 但不会真正加锁. 然后, 如果出现锁竞争的话, "偏向锁" 会升级到 "轻量级锁", 之后, 线程进一步统计锁竞争的次数和频率, 当达到一定程度的时候, "轻量级锁" 就会升级到 "重量级锁".

(synchronized加锁过程: 无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁)

[注]: 偏向锁, 并不会真的加锁, 而只是"做一个标记", ++标记的过程, 开销很小, 非常的轻量和高效++.

八. 锁消除

"锁消除"机制 是编译器的自动优化策略. 比如我们写了一个带synchronized锁的代码 , 编译器就会对我们加锁的代码做出判定, 如果判断得到这里没有必要加锁, 就会自动把这里的锁消除掉.

九. 锁粗化

在说锁粗化之前, 我们首先得明确一个概念: "锁的粒度". --> 一个锁保护的代码范围越广 , 那么这个锁的粒度就越粗 ; 一个锁保护的代码范围越小 , 那么这个锁的粒度就越细 .

那么锁的粗化, 就是把多个"细粒度"的锁, 合成粗粒度的锁. 当编译器检测到*++一系列连续的操作都在对同一个锁进行加锁和解锁时, 会将这些操作合并为一个更大的锁区域, 从而减少锁的开销.++*

锁粗化的优点: 减少了对锁的获取和释放次数, 降低了上下文切换和调度的开销, 减少了线程因频繁获取和释放锁而产生的竞争, 提高了程序的吞吐量。

相关推荐
袁庭新12 分钟前
LuaRocks如何安装数据库驱动?
java·数据库·redis·lua·luarocks·袁庭新
hummhumm20 分钟前
第 10 章 - Go语言字符串操作
java·后端·python·sql·算法·golang·database
开发者每周简报29 分钟前
当微软windows的记事本被AI加持
人工智能·windows·microsoft
nukix31 分钟前
Mac Java 使用 tesseract 进行 ORC 识别
java·开发语言·macos·orc
月光光心慌慌。34 分钟前
新日撸java三百行` 新手小白java学习记录 `Day1
java
命里有定数37 分钟前
windows工具 -- 使用rustdesk和云服务器自建远程桌面服务, 手机, PC, Mac, Linux远程桌面 (简洁明了)
linux·运维·服务器·windows·ubuntu·远程工作
蘑菇丁38 分钟前
ranger-kms安装
java·ide·eclipse
XiaoLeisj40 分钟前
【JavaEE初阶 — 多线程】内存可见性问题 & volatile
java·开发语言·java-ee
weixin_462428471 小时前
使用 Caffeine 缓存并在业务方法上通过注解实现每3到5秒更新缓存
java·缓存
程序媛小果1 小时前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot