快速了解Java中的15把锁!
目录
了解
总览
乐观锁
悲观锁
互斥锁和同步锁
公平锁
非公平锁
自旋锁
可重入锁(递归锁)
ReadWriteLock读写锁
共享锁
独占锁
偏向锁
轻量级锁
重量级锁
锁优化
在 Java 中,锁是一种用于实现多线程之间同步和互斥的机制。
了解
一、定义
锁是一种抽象的概念,用于控制对共享资源的访问。它确保在同一时间只有一个线程可以访问被锁保护的代码块或对象。Java 中的锁可以是显式的(如通过ReentrantLock类实现)或隐式的(如使用synchronized关键字)。
二、产生
-
多线程编程的需求:
- 在多线程环境下,多个线程可能同时访问共享资源。如果没有适当的同步机制,可能会导致数据不一致、竞态条件等问题。为了解决这些问题,Java 引入了锁的概念。
- 例如,一个银行账户对象可能被多个线程同时访问来进行存款和取款操作。如果没有锁来控制对账户余额的访问,可能会出现错误的结果。
-
语言设计的一部分:
- Java 语言的设计者认识到多线程编程的复杂性和潜在问题,因此在语言中提供了内置的同步机制,如
synchronized关键字和java.util.concurrent.locks包中的显式锁。这些机制使得开发人员能够更容易地实现线程安全的代码。
- Java 语言的设计者认识到多线程编程的复杂性和潜在问题,因此在语言中提供了内置的同步机制,如
三、作用
-
保证线程安全:
- 锁确保了对共享资源的互斥访问,防止多个线程同时修改同一数据,从而保证了数据的一致性。
- 例如,在一个多线程的计数器程序中,使用锁可以确保在多个线程同时增加计数器值时,不会出现错误的结果。
-
避免竞态条件:
- 竞态条件是指多个线程同时访问和修改共享资源时,由于执行顺序的不确定性而导致的错误结果。锁可以避免竞态条件的发生,通过确保线程在访问共享资源时的正确顺序。
- 例如,在一个生产者 - 消费者问题中,使用锁可以确保生产者在向缓冲区添加数据时,消费者不会同时从缓冲区中取出数据,从而避免了缓冲区的混乱。
-
实现线程间的同步:
- 锁不仅可以用于互斥访问,还可以用于实现线程之间的同步。例如,一个线程可以等待另一个线程完成某个任务后再继续执行。
- 例如,在一个多线程的任务处理系统中,一个主线程可以使用锁来等待所有子线程完成任务后再进行下一步操作。
总之,Java 中的锁是一种重要的多线程编程机制,它可以帮助开发人员实现线程安全的代码,避免竞态条件的发生,并实现线程之间的同步。
总览
| 序号 | 锁名称 | 应用 |
|---|---|---|
| 1 | 乐观锁(Optimistic Locking) | CAS |
| 2 | 悲观锁(Pessimistic Locking) | synchronized、vector、hashtable |
| 3 | 同步锁 | synchronized |
| 4 | 互斥锁(Mutex) | synchronized、Reentrantlock |
| 5 | 公平锁(FairLock) | Reentrantlock(true) |
| 6 | 非公平锁 | synchronized、Reentrantlock(false) |
| 7 | 自旋锁 | CAS |
| 8 | 可重入锁(ReentrantLock) | synchronized、Reentrantlock、Lock |
| 9 | 读写锁(ReadWriteLock) | ReentrantReadWriteLock,CopyOnWriteArrayList、CopyOnWriteArraySet |
| 10 | 共享锁 | ReentrantReadWriteLock中读锁 |
| 11 | 独占锁 | synchronized、vector、hashtable、ReentrantReadWriteLock中写锁 |
| 12 | 偏向锁(Biased Locking) | 锁优化技术 |
| 13 | 重量级锁 | synchronized |
| 14 | 轻量级锁 | 锁优化技术 |
| 15 | 分段锁 | concurrentHashMap |
乐观锁
-
是一种乐观思想,抽象的锁,认为读多写少,每次拿数据时默认对方不会修改数据。
-
但是会在更新的时候进行判断期间是否有其他线程对其进行修改,写之前读取当前版本号,一样则更新,不一样重复 读-比较-写 的操作。
-
基本通过CAS实现。CAS是一种原子操作,比较当前值跟传入值一样则更新,否则失败
悲观锁
-
是一种悲观思想,抽象的锁,认为写多读少,每次拿数据都认为别人会修改。
-
所以每次读取时都会上锁,这样别人想读写这个数据时会Block至拿到锁
-
JAVA中悲观锁就是synchronized;AQS(AbstractQueuedSynchronizer)框架下的锁先尝试CAS乐观锁去获取锁,获取不到才转换为悲观锁,如RetreenLock
互斥锁和同步锁
都是悲观锁
同步锁在很多情况下可以理解为是一种互斥锁,但它们并不完全等同。
一、相似之处
-
互斥性:
-
同步锁和互斥锁都具有互斥的特性,即在同一时间只允许一个线程访问被保护的资源或代码区域。
-
例如,使用
synchronized关键字修饰的方法或代码块,以及使用ReentrantLock实现的锁,都能确保在同一时刻只有一个线程能够执行被锁保护的代码,这与互斥锁的行为一致。
-
-
保证数据一致性:
-
两者都是为了防止多个线程同时访问共享资源时出现数据不一致的问题。通过对资源的互斥访问,确保线程在修改共享数据时不会被其他线程干扰。
-
二、不同之处
-
概念范围:
-
同步锁更侧重于实现线程之间的同步操作,确保线程按照特定的顺序执行或访问共享资源。同步可以包括多种方式,如互斥锁只是其中一种实现同步的手段。
-
例如,除了互斥锁,还可以通过条件变量、信号量等方式实现线程同步,但这些方式不一定完全等同于互斥锁。
-
-
实现方式:
-
同步锁可以通过多种方式实现,如
synchronized关键字、ReentrantLock等。这些实现方式在某些方面可能有不同的特性和功能。 -
互斥锁通常更强调对资源的独占访问,实现方式相对较为单一,主要是通过类似
ReentrantLock这样的明确的互斥锁机制来实现。
-
综上所述,同步锁和互斥锁有很多相似之处,但在概念范围和实现方式上存在一些差异。在很多情况下,同步锁可以表现为互斥锁的行为,但不能简单地认为同步锁就是互斥锁。
公平锁
加锁前检查有没有排队等待的线程,优先排队等待的线程,先来先得
多核情况需要维护一个队列,性能低于非公平锁
非公平锁
加锁时,不考虑排队情况,都尝试获取锁
性能高于公平锁5-10倍
synchronized只能非公平锁,ReentrantLock默认非公平锁
自旋锁
-
简单来说就是线程自旋不断尝试获得所需锁。
-
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗
-
线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
自适应自旋锁
-
1.6引入,自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,将线程上下文切换所需的时间作为一个参考标准。大于上下文切换所需时间,那就避免自旋,直接进入堵塞等待状态,避免CPU资源损耗。
可重入锁(递归锁)
同一线程可多次请求同一把锁,不会死锁被堵塞。
ReentrantLock 和 synchronized 都是 可重入锁
假设有一个同步方法 methodA ,在其内部又调用了自身 methodA 。如果使用的锁是可重入锁,那么同一个线程在第一次获取锁进入 methodA 后,再次调用 methodA 时可以再次获取到锁,而不会被阻塞。
ReadWriteLock读写锁
为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制
共享锁
也被成为读锁,允许多个线程同时获得锁,并发访问共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
独占锁
-
每次只能一个线程持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
-
独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性
关于独占锁中的“读读冲突” “读读冲突”并非指单纯的读操作之间会直接导致数据不一致的冲突。在独占锁的场景下,所谓的“读读冲突”更多是从锁的机制和资源竞争的角度来理解的。 当使用独占锁时,无论线程进行的是读操作还是写操作,锁的规则都是一次只允许一个线程获取到锁。 即使多个线程都是只读操作,如果一个线程获取了独占锁,其他线程也只能等待。这是因为独占锁的设计原则就是在同一时间只允许一个线程持有锁来访问被保护的资源或代码区域。 例如,假设系统中有一个共享的数据结构,虽然读操作不会改变其数据,但如果多个读线程同时进行操作,可能会存在一些潜在的问题。比如,可能会导致频繁的缓存失效,或者在一些复杂的并发环境中,可能会引发一些难以预测的时序问题。 再比如,从锁的实现和管理角度来看,为了确保锁的机制简单且可靠,独占锁不会区分读操作和写操作,统一按照独占的方式进行处理。 所以,在独占锁的机制下,即使只是读操作,一旦一个线程获取了锁,其他线程也需要等待,以保证锁的管理和资源访问的有序性和确定性。
偏向锁
-
只有一个线程执行,锁会偏向给线程,避免多次CAS造成资源浪费。
-
只需要每次置换ThreadID(线程ID)的时候依赖一次CAS原子指令
偏向锁是 Java 虚拟机中的一种锁优化机制。 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁就是基于这种假设,当一个线程获取锁时,会将锁标记为偏向于这个线程,下次该线程再次请求这个锁时,无需再进行同步操作,从而提高性能。 偏向锁的主要目的是在没有多线程竞争的情况下,减少不必要的同步开销。 应用场景主要包括以下几种: 1. 单线程环境:如果程序在大多数情况下都是单线程运行,例如一些只在初始化阶段进行配置,后续主要由单个线程操作的对象。 2. 线程局部对象:某些对象的操作主要局限于特定的线程,其他线程很少甚至不会访问。 例如,在一个日志处理程序中,有一个专门用于记录日志信息的对象,大部分时间只有一个线程在操作它来写入日志,这种情况下就适合使用偏向锁来提高性能。 需要注意的是,偏向锁在存在多线程竞争时会升级为轻量级锁或重量级锁,以保证线程安全。
轻量级锁
-
多个线程交替执行,但是不针对同一对象,通过自旋等待获取锁,避免上下文切换造成的资源消耗
-
如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
重量级锁
-
多个线程并发执行,且针对同一对象,避免自旋时间过久,也就是无用自旋造成的资源浪费
-
通过对象内部监视器锁(monitor)来实现。监视器锁又是依赖操作系统(互斥量)Mutex Lock来实现。
-
操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间
分段锁
-
分段锁将一个数据结构或资源划分成多个段(segment),每个段都有自己独立的锁。这样,不同的线程可以同时访问不同段上的数据,而不会相互阻塞,从而提高并发度。
-
锁的设计,并不是具体的一种锁,像 ConcurrentHashMap就使用到了
锁优化
减少锁持有时间
只用在有线程安全要求的程序上加锁
减小锁粒度
将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。
降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是
ConcurrentHashMap。
锁分离
最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互
斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如
LinkedBlockingQueue 从头部取出,从尾部放数据
锁粗化
通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。锁粗化是一种优化技术,指的是在程序执行过程中,当一系列连续的对同一锁的操作被检测到时,虚拟机可能会将这些锁操作合并为一个范围更大的锁操作,以减少锁获取和释放的次数,从而提高性能。
锁消除
锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起。
相关文章:
快速了解Java中的15把锁!
目录 了解 总览 乐观锁 悲观锁 互斥锁和同步锁 公平锁 非公平锁 自旋锁 可重入锁(递归锁) ReadWriteLock读写锁 共享锁 独占锁 偏向锁 轻量级锁 重量级锁 锁优化 在 Java 中,锁是一种用于实现多线程之间同步和互斥的机制。 了…...
TypeScript 封装 Axios 1.7.7
随着Axios版本的不同,类型也在改变,以后怎么写类型? yarn add axios1. 封装Axios 将Axios封装成一个类,同时重新封装request方法 重新封装request有几个好处: 所有的请求将从我们定义的requet请求中发送ÿ…...
【数据结构】【链表代码】移除链表元素
移除链表元素 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* removeElements(struct ListNode* head, int val) { // 创建一个虚拟头节点,以处理头节点可能被删除的情况 struct…...
作文-杭州游记
杭州的学习与游历 在这个风景如画的城市——杭州,学习信息学的日子如同西湖的水,清澈而又深邃。在这里,课堂与自然的交融、技术与文化的碰撞,构成了一幅独特的画卷。 学习之旅 信息学的课程不仅仅是对代码和算法的解析࿰…...
降压芯片TPS54821
降压芯片TPS54821 介绍 价格低廉,只需1.5元。是一个同步整流降压BUCK电路。MOS管内置。输入电压为4.5V至17V,输出电压为0.6V到15V,输出电流最大到8A。是QFN封装,焊接时有些许困难。得益于QFN封装,其引线电感非常的小…...
YOLO v1详解解读
🚀 在此之前主要介绍了YOLO v5源码的安装和使用(YOLO v5安装教程),接下来将探索YOLO的实现原理,作为一个金典的单阶段目标检测算法,应该深度的理解它的构建思想;所以本系列文章将从LOVO v1出发到…...
【动态规划-最长公共子序列(LCS)】【hard】【科大讯飞笔试最后一题】力扣115. 不同的子序列
给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 10^9 7 取模。 示例 1: 输入:s “rabbbit”, t “rabbit” 输出:3 解释: 如下所示, 有 3 种可以从 s 中得到 “rabbit”…...
深入理解 JavaScript 中的 void`运算符和 yield*表达式
深入理解 JavaScript 中的 void 运算符和 yield* 表达式 在 JavaScript 中,void 运算符和 yield* 表达式是两个功能独特但常被忽视的运算符。本文将详细介绍它们的用法和应用场景,帮助您更好地理解和运用这两个运算符。 目录 void 运算符概述void 运算…...
第四节——从深层剖析指针(让你不再害怕指针)
文章目录 1. 字符指针变量剑指offer例题 2. 数组指针变量2.1 数组指针变量是什么?2.2 数组指针变量怎么初始化 3. ⼆维数组传参的本质代码实现 4. 函数指针变量4.1 函数指针变量的创建4.3 两段有趣的代码4.3.1 typedef 关键字 5. 函数指针数组的定义 1. 字符指针变量…...
openpnp - 吸嘴校正失败的opencv参数分析
文章目录 openpnp - 吸嘴校正失败的opencv参数分析概述笔记阶段验证 - N2吸嘴校验完NT1NT2 阶段验证 - 底部相机高级校验完NT1NT2 参数比对保存 “阶段验证 - N2吸嘴校验完” 的NT1/NT2图像重建参数检测环境NT1ok的3个参数值NT1err的3个参数值NT2ok的3个参数值NT2err的3个参数值…...
【Python】Marmir 使用指南:Python 驱动的电子表格生成器
Marmir 是一个由 Python 驱动的电子表格生成工具,专门用于将 Python 数据结构(如字典、列表等)转换为电子表格文件(如 Excel)。Marmir 的设计目标是提供比传统电子表格库(如 xlwt)更强大和灵活的…...
深入理解 JavaScript 事件循环机制:单线程中的异步处理核心
深入理解 JavaScript 事件循环机制:单线程中的异步处理核心 JavaScript 是一门单线程的编程语言,也就是说它在同一时间只能执行一个任务。然而,现代 Web 应用经常需要处理大量的异步操作,如用户输入、网络请求、定时器等。为了确…...
Stream流的终结方法(二)——collect
1.Stream流的终结方法 2. collect方法 collect方法用于收集流中的数据放到集合中去,可以将流中的数据放到List,Set,Map集合中 2.1 将流中的数据收集到List集合中 package com.njau.d10_my_stream;import java.util.*; import java.util.f…...
【C语言系统编程】【第一部分:操作系统知识】1.1.操作系统原理
第一部分:操作系统知识 1.1 操作系统原理 1.1.1 进程管理 1.1.1.1 进程的概念与生命周期 进程是程序在计算机中的一次执行实例,包括了程序的代码、数据、以及运行的上下文环境。进程管理是操作系统的核心任务之一。 作用:管理所有执行中…...
基于Java+VUE+echarts大数据智能道路交通信息统计分析管理系统
大数据智能交通管理系统是一种基于Web的系统架构,通过浏览器/服务器(B/S)模式实现对城市交通数据的高效管理和智能化处理。该系统旨在通过集成各类交通数据,包括但不限于车辆信息、行驶记录、违章情况等,来提升城市管理…...
leetcode-42. 接雨水 单调栈
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…...
ThinkPHP和PHP的区别
文章目录 ThinkPHP和PHP的区别一、引言二、PHP简介1、第一步1.1、示例代码 三、ThinkPHP简介2、第二步2.1、特点2.2、示例代码 四、总结 ThinkPHP和PHP的区别 一、引言 在Web开发领域,PHP是一种广泛使用的开源脚本语言,而ThinkPHP则是一个基于PHP的MVC…...
clientWidth,offsetWidth,scrollHeight
clientWidth: offsetWidth: scrollHeight:...
SVN版本回退
SVN 版本回退三种方法: Update item to this version 假设我们的项目文件一共有8个版本,它版本号分别是1,2,3,4,5,6,7,8。 这个选项的作用是将文件版本更新到对应所选的…...
IDEA关联Tomcat
一、Tomcat服务器 web服务器,就是运行web项目的容器 即运行java代码的一个容器 webapp(web应用程序) --> 就是我们写的javaweb项目 Tomcat 是Apache 软件基金会(Apache Software Foundation)下的一个核心项目,免费开源、并支持Servlet 和J…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...
