多线程之常见的锁策略

简介: 多线程之常见的锁策略

一、乐观锁 VS 悲观锁

站在锁冲突概率的角度来看,冲突较少,锁竞争不激烈,此时为乐观锁,反之,锁竞争很激烈则升级为悲观锁。

对于乐观锁来说,假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何做。

对于悲观锁来说,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

这两种思路不能说谁优谁劣, 而是看当前的场景是否合适。

synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略。

乐观锁的一个重要功能就是要检测出数据是否发生访问冲突,我们可以引入一个 "版本号" 来解决,只要访问一次,版本号就会增加一次,所以版本号是唯一的,可以用来校验。


二、互斥锁 VS 读写锁


关于互斥锁,就是像synchronized这样的锁,提供加锁和解锁两个操作。如果一个线程加锁了,另一个线程也尝试加锁,就会阻塞等待。

关于读写锁,提供了三种操作:针对读加锁、针对写加锁、解锁。因为多线程针对同一个变量进行并发读,这个时候没有线程安全问题的,也不需要加锁控制。读锁和读锁之间,没有互斥;写锁和写锁之间,存在互斥;写锁和读锁之间,存在互斥。

在代码中,如果只是读操作,加读锁即可,如果有写操作,加写锁。

假设如果当前有一组线程都去读(加读锁),这些线程之间没有锁竞争的,也没有线程安全问题。

假设如果当前的一组操作有读也有写,才会产生锁竞争。

在很多开发场景中,读操作非常高频,比写操作的频率高很多,读写锁特别适合于 "频繁读, 不频繁写" 的场景中。synchronized 不是读写锁。


三、轻量级锁 VS 重量级锁


站在加锁操作的开销角度来看,轻量级锁的开销较小,重量级锁的开销较大。


重量级锁的加锁机制严重依赖了操作系统,很容易造成线程的调度,造成大量的内核态和用户态切换(内核态可以想象成银行柜台的工作人员,用户态可以想象成顾客,办理一个业务,频繁的切换工作人员和顾客,这样到最后办理这样一个业务的开销会很大)。而轻量级锁的加锁机制是尽量不会依赖操作系统去切换内核态和用户态。

synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁。


四、自旋锁 VS 挂起等待锁

关于自旋锁线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU。这个时候就可以使用自旋锁来处理这样的问题,一直循环等待。

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止。第一次获取锁失败, 第二次的尝试会

在极短的时间内到来。一旦锁被其他线程释放, 就能第一时间获取到锁。

关于挂起等待锁,一旦抢锁失败后就放弃 CPU了,过了一会儿再次获取锁,就算抢锁成功了,此时已经“沧海桑田”了,举个例子:

当男生向女神表白后, 女神说: 你是个好人, 但是我有男朋友了!

挂起等待锁: 陷入沉沦不能自拔,过了很久很久之后, 突然女神发来消息, "咱俩要不试试?" (注意,

这个很长的时间间隔里, 女神可能已经换了好几个男票了)。

自旋锁: 死皮赖脸坚韧不拔,仍然每天持续的和女神说早安晚安。 一旦女神和上一任分手, 那么就能

立刻抓住机会上位。

自旋锁是一种典型的 轻量级锁 的实现方式:

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁。

缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源。 (而挂起等待的时候是

不消耗 CPU 的)。

synchronized 中的轻量级锁策略可能就是通过自旋锁的方式实现的。


五、公平锁 VS 非公平锁

此处的公平可以理解为“先来后到”的原则,举个例子:

女神正在和舔dog1处对象,但是此时正在排队的有:dog2舔了一年,dog3舔了半年,dog4舔了一个月,dog5舔了一周。当女神和dog1分手后,那么dog2就上位扶正,依次类推。这就是公平锁。

反之,当女神和dog1分手后,dog2、dog3、dog4、dog5随机上位,每个人的机会是公平的,但是这对于舔了很久的dog们来说是不公平的,这就是非公平锁。

操作系统内部的线程调度就可以视为是随机的。 如果不做任何额外的限制, 锁就是非公平锁。如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序。

公平锁和非公平锁没有好坏之分, 关键还是看适用场景。

synchronized 是非公平锁。


六、可重入锁 VS 不可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁

比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入

(因为这个原因可重入锁也叫做递归锁

Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括

synchronized关键字锁都是可重入的。

或者

不可重入锁:一个线程针对一把锁连续加锁两次,出现死锁。

可重入锁:一个线程针对一把锁,连续加锁多次都不会死锁。



相关文章
|
15天前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
36 0
|
12天前
|
调度 C语言
深入浅出:C语言线程以及线程锁
线程锁的基本思想是,只有一个线程能持有锁,其他试图获取锁的线程将被阻塞,直到锁被释放。这样,锁就确保了在任何时刻,只有一个线程能够访问临界区(即需要保护的代码段或数据),从而保证了数据的完整性和一致性。 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含一个或多个线程,而每个线程都有自己的指令指针和寄存器状态,它们共享进程的资源,如内存空间、文件句柄和网络连接等。 线程锁的概念
|
3天前
|
安全 算法 Linux
【Linux】线程安全——补充|互斥、锁|同步、条件变量(下)
【Linux】线程安全——补充|互斥、锁|同步、条件变量(下)
12 0
|
3天前
|
存储 安全 Linux
【Linux】线程安全——补充|互斥、锁|同步、条件变量(上)
【Linux】线程安全——补充|互斥、锁|同步、条件变量(上)
10 0
|
4天前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
14 0
|
5天前
|
Java 开发者
Java面试题:Java内存管理精要与多线程协同策略,Java内存管理:堆内存、栈内存、方法区、垃圾收集机制等,多线程编程的掌握,包括线程创建、同步机制的原理
Java面试题:Java内存管理精要与多线程协同策略,Java内存管理:堆内存、栈内存、方法区、垃圾收集机制等,多线程编程的掌握,包括线程创建、同步机制的原理
10 0
|
5天前
|
监控 Java
Java面试题:Java内存、多线程与并发工具包的深度探索,Java内存管理策略及其优化技巧,Java多线程并发控制的工具类与机制,Java并发工具包在实际项目中的应用
Java面试题:Java内存、多线程与并发工具包的深度探索,Java内存管理策略及其优化技巧,Java多线程并发控制的工具类与机制,Java并发工具包在实际项目中的应用
10 0
|
5天前
|
存储 缓存 Java
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
8 0
|
5天前
|
算法 安全 Java
Java面试题:解释JVM中的堆内存分代收集策略,并讨论年轻代和老年代的特点,描述Java中的线程池,并解释线程池的优点,解释Java中的`volatile`关键字的作用和使用场景
Java面试题:解释JVM中的堆内存分代收集策略,并讨论年轻代和老年代的特点,描述Java中的线程池,并解释线程池的优点,解释Java中的`volatile`关键字的作用和使用场景
6 0
|
5天前
|
安全 Java
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
9 0