锁策略总结

锁策略

悲观锁和乐观锁

乐观锁和悲观锁不是具体类型的锁而是指两种不同的对待加锁的态度,这两个锁面对锁冲突的态度是相反的。

  • 乐观锁:认为不存在很多的并发操作,因此不需要加锁。
  • 悲观锁:认为存在很多并发操作,因此需要加锁。

重量级锁和轻量级锁

重量级锁和轻量级锁是通过结果去看待加锁解锁的过程开销。做的工作多消耗资源多,做的工作少消耗资源少。因此我们会认为乐观锁是轻量级锁,悲观锁是重量级锁。

  • 重量级锁:是进入内核态的加锁逻辑,资源开销较大。
  • 轻量级锁:是纯用户态的加锁逻辑,资源开销较小。  

自旋锁和挂起等待锁

  • 自旋锁:是一种轻量级锁,自旋锁类是一直反复查看当前锁是否就绪,CPU不停空转会消耗大量地CPU。
  • 挂起等待锁:是一种重量级锁,该锁不会像自旋锁一样一直查看锁状态而是先去做别的事情,过一会再来看当前锁是否就绪。

互斥锁和读写锁

  • 互斥锁:当两个线程竞争同一把锁时会产生锁冲突进而阻塞等待。
  • 读写锁:根据代码实际的逻辑进行加锁,有读锁和写锁两种锁。读锁和读锁之间不会产生锁竞争;读锁和写锁之间会产生锁竞争;写锁和写锁之间,会产生锁竞争。如果代码中读的场景多写的场景少时,读写锁相比于互斥锁优化了效率、减少了不必要的锁竞争。

可重入锁和不可重入锁 

  • 不可重入锁:是指同一个线程对同一把锁连续加锁两次,产生了死锁。
  • 可重入锁:是指同一个线程在外层方法获取锁,进入内层方法会自动获取锁。

注ReentrantLock和Synchronized都是可重入锁。

公平锁和非公平锁

  • 公平是指遵循先来后到的原则的,因此遵守先来后到就是公平锁,不遵守先来后到的就是非公平锁。
  • 例:t1,t2,t3三个线程竞争同一把锁,谁先来的谁就拿到锁的情况叫公平锁。如果三个线程随机一个拿到锁,后来的线程可能会先拿到锁的情况叫非公平锁。
  • 公平锁:指多个线程按照申请锁的顺序来获取锁。
  • 非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。

 操作系统默认的锁的调度是非公平的。系统对于线程的调度是随机的,自带的synchronized这个锁是非公平的。想要实现公平锁需要在synchronized的基础上,加个队列来记录这些加锁线程的顺序。

cas 和 synchronized 优化过程

CAS全称: compare and swap. 根据字面意思理解为"比较并交换"

一个CAS涉及到以下操作:

假设内存中的原数据是V,旧的预期值是A,需要修改的新值是B

1).先比较A和V是否相等(比较)

2).如果比较相等,就将B写入V(交换)

3).返回操作是否成功

 

在上述交换过程中,大多数情况下并不关心B后续的情况,更关心的是V这个变量的情况

这里说是交换,其实可以近似理解成:赋值

此处最特别的地方是:上述这个CAS过程并非是通过一段代码实现的,而是通过一条CPU指令完成的,这说明CAS操作是原子的!!!!!

CAS可以理解成是CPU提供的一个特殊指令,通过这个指令可以一定程度上处理线程安全问题.

CAS的伪代码:

boolean CAS(value, oldValue, swapValue) {if (value == oldValue) {value = swapValue;return true;}return false;
}

CAS的应用场景

CAS的应用场景主要有两个:

1).实现原子类

     java标准库提供了java.util.concurrent.atomic包,里面的类都是基于CAS的方式实现的,其中最典型的就是AtomicInteger类,其中的getAndIncrement相当于i++操作

    通过下面一个练习来理解原子类:

    两个线程增加同一个变量

public class Exercise {public static void main(String[] args) throws InterruptedException {AtomicInteger integer = new AtomicInteger(0);Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {integer.getAndIncrement();// 相当于i++操作}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {integer.getAndIncrement();// 相当于i++操作}});t1.start();t2.start();// 为了让主线程等待t1线程和t2线程执行完毕t1.join();t2.join();System.out.println(integer);}
}

实现自旋锁

通过下面的伪代码理解:

public class SpinLock { private Thread owner = null; public void lock(){// 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就自旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner = null;}
}

ABA问题

CAS运行的核心:检查value与oldValue是否一致.如果一致,就视为value中途没有被修改过,所以进行下一步交换操作是没问题的.

但是,这里的一致,可能是value没有被修改过,也有可能是value被修改过又改回来了. 

把value的值设为A的话,CAS判定value为A,此时value的值可能始终为A,也可能是value本来是A,被修改为了B,最后又还原成了A.

这就是CAS的典型问题:ABA问题

假设 张三 有 100 存款 . 张三 想从 ATM 取 50 块钱 . 取款机创建了两个线程 , 并发的来执行 -50
操作 .
我们期望一个线程执行 -50 成功 , 另一个线程 -50 失败 .
如果使用 CAS 的方式来完成这个扣款过程就可能出现问题 .
正常的过程
1) 存款 100. 线程 1 获取到当前存款值为 100, 期望更新为 50; 线程 2 获取到当前存款值为 100, 期
望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50. 线程 2 阻塞等待中 .
3) 轮到线程 2 执行了 , 发现当前存款为 50, 和之前读到的 100 不相同 , 执行失败 .

异常的过程
1) 存款 100. 线程 1 获取到当前存款值为 100, 期望更新为 50; 线程 2 获取到当前存款值为 100, 期
望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50. 线程 2 阻塞等待中 .
3) 在线程 2 执行之前 ,张三 的朋友正好给张三转账 50, 账户余额变成 100 !!
4) 轮到线程 2 执行了 , 发现当前存款为 100, 和之前读到的 100 相同 , 再次执行扣款操作
这个时候 , 扣款操作被执行了两次 !!! 

 

解决ABA问题的方法:给要修改的值,引入版本号

在 CAS 比较数据当前值和旧值的同时 , 也要比较版本号是否符合预期 .
CAS 操作在读取旧值的同时 , 也要读取版本号 .
真正修改的时候:
如果当前版本号和读到的版本号相同 , 则修改数据 , 并把版本号 + 1.
如果当前版本号高于读到的版本号 . 就操作失败 ( 认为数据已经被修改过了 ).
引入版本号后,解决刚才转账异常的情况:
假设 张三 有 100 存款 . 张三 想从 ATM 取 50 块钱 . 取款机创建了两个线程 , 并发的来执行 -50
操作 .
我们期望一个线程执行 -50 成功 , 另一个线程 -50 失败 .
为了解决 ABA 问题 , 给余额搭配一个版本号 , 初始设为 1.
1) 存款 100. 线程 1 获取到 存款值为 100, 版本号为 1, 期望更新为 50; 线程 2 获取到存款值为 100,
版本号为 1, 期望更新为 50.
2) 线程 1 执行扣款成功 , 存款被改成 50, 版本号改为 2. 线程 2 阻塞等待中 .
3) 在线程 2 执行之前 , 张三 的朋友正好给滑稽转账 50, 账户余额变成 100, 版本号变成 3.
4) 轮到线程 2 执行了 , 发现当前存款为 100, 和之前读到的 100 相同 , 但是当前版本号为 3, 之前读
到的版本号为 1, 版本小于当前版本 , 认为操作失败 .

Synchronized 

1).synchronized既是一个悲观锁,也是一个乐观锁

    synchronized默认是乐观锁,但是如果发现锁竞争激烈,就会变成悲观锁

2).synchronized既是一个轻量级锁,也是一个重量级锁

    synchronized默认是轻量级锁,但是如果发现锁竞争激烈,就会变成重量级锁

3).synchronized这里的轻量级锁,是基于自旋锁的方式实现的.

    synchronized这里的重量级锁,是基于挂起等待锁的方式实现的.

4).synchronized 不是读写锁

5).synchronized 是非公平锁

6).synchronized 是可重入锁


jvm将synchronized 锁分为:无锁 偏向锁 轻量级锁 重量级锁 状态,会根据情况,进行依次升级.

1).偏向锁

     第一个尝试加锁的线程,优先进入偏向锁状态.偏向锁不是真的"加锁",而是做个偏向锁标记,记录这个锁属于哪个线程.

    如果整个使用锁的过程中,没有出现锁竞争,在synchronized执行完之后,取消偏向锁即可

    如果整个使用锁的过程中.另一个线程也尝试加锁,那么在它加锁之前,迅速的把偏向锁状态升级为加锁状态,另一个线程只能阻塞等待了.


2).轻量级锁

     随着其它线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁)

     此处的轻量级锁就是通过CAS来实现的.

     自旋操作是一直让CPU空转,比较浪费CPU资源,因此,此处的自旋不会一直持续进行,而是达到一定的时间/重试次数,就不在自旋了,也就是所谓的"自适应".


3).重量级锁

    如果竞争进一步激烈,自旋不能快速获取到锁,就会升级为重量级锁

    此处的重量级锁就是指用到内核提供的mutex

之后还有关于Synchronized 的内容,下次放送。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/620685.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ffmpeg命令与批处理编程

(一) CMD脚本查找所有文件 powershell与cmd转换 powershell与cmd虽然同为windows命令&#xff0c;但许多命令并不通用。 CMD换行符 a 在CMD下&#xff0c;可以用^作为换行符&#xff0c;类似于Linux下的\。举例如下&#xff1a; start pemu.exe ^ -net nic,vlan1,macaddr…

OpenKylin设置root密码

前言 新安装的OpenKylin系统应该root用户没有设置密码&#xff0c;但是可以使用sudo -i 临时获取root权限&#xff0c;不影响正常使用 当前是root用户 1、终端输入passwd命令 passwd2、按照提示输入新密码和确认密码 当前非root用户 1、终端输入sudo passwd root 命令 s…

设计模式面试题

概述 设计模式分类 创建型模式 用于描述“怎样创建对象”&#xff0c;主要特点是“将对象的创建与使用分离”。使用者不需要官族对象创建的细节。结构型模式 用于描述如何将类或对象按照某种布局组成更大的结构。行为型模式 用于描述类或对象之间怎样相互协作共同完成单个对象…

没有学历,没有基础,是否能成为一个嵌入式工程师?

没有学历&#xff0c;且毫无基础&#xff0c;是否真的能够成为一名嵌入式工程师呢&#xff1f;想要成为一名真正足以胜任上下游工作的工程师&#xff0c;至少是需要耗费五年时间的。在这一过程当中&#xff0c;你将会遭遇各式各样的挑战&#xff0c;需要去掌控操作系统、数据结…

实时数据同步之Maxwell和Canal

文章目录 一、概述1、实时同步工具概述1.1 Maxwell 概述1.2 Canal概述 2、数据同步工作原理2.1 MySQL 主从复制过程2.2 两种工具工作原理 3、MySQL 的 binlog详解3.1 什么是 binlog3.2 binlog 的开启3.3 binlog 的分类设置 4、Maxwell和Canal对比5、环境安装 二、Maxwell 使用1…

护眼台灯哪个牌子好?精选五款备受好评性价比高的护眼灯

众所周知&#xff0c;用眼习惯是影响视力的一大因素&#xff0c;但往往忽略了光线对视力的关键作用。孩子在白天长时间用眼&#xff0c;若晚上又在昏暗环境下读书、写作或使用电子产品&#xff0c;这种双重负担会加剧眼睛疲劳&#xff0c;进而可能导致视力逐渐受损。随着熬夜学…

部署ELFK+zookeeper+kafka架构

目录 前言 一、环境部署 二、部署ELFK 1、ELFK ElasticSearch 集群部署 1.1 配置本地hosts文件 1.2 安装 elasticsearch-rpm 包并加载系统服务 1.3 修改 elasticsearch 主配置文件 1.4 创建数据存放路径并授权 1.5 启动elasticsearch是否成功开启 1.6 查看节点信息 …

Springboot+Vue项目-基于Java+Mysql的网上订餐系统(附源码+LW+演示录像)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

太好玩了,我用 Python 做了一个 ChatGPT 机器人

毫无疑问&#xff0c;ChatGPT 已经是当下编程圈最火的话题之一&#xff0c;它不仅能够回答各类问题&#xff0c;甚至还能执行代码&#xff01; 或者是变成一只猫 因为它实在是太好玩&#xff0c;我使用Python将ChatGPT改造&#xff0c;可以实现在命令行或者Python代码中调用。…

即席查询笔记

文章目录 一、Kylin4.x1、Kylin概述1.1 定义1.2 Kylin 架构1.3 Kylin 特点1.4 Kylin4.0 升级 2、Kylin 环境搭建2.1 简介2.2 Spark 安装和部署2.3 Kylin 安装和部署2.4 Kylin 启动环境准备2.5 Kylin 启动和关闭 3、快速入门3.1 数据准备3.2 Kylin项目创建入门3.3 Hive 和 Kylin…

金蝶云星空与泛微OA对接案例-实现流程一体化

摘要&#xff1a;在企业数字化中&#xff0c;集成内部各业务系统以实现数据共享与流程协同&#xff0c;已成为提升运营效率、优化决策的重要选择。本文将以某企业成功实现金蝶云星空与泛微OA系统对接为例&#xff0c;详细解析双方在人员信息、组织架构、销售合同、付款申请、报…

算法打卡day41|动态规划篇09| Leetcode198.打家劫舍、213.打家劫舍II、337.打家劫舍 III

算法题 Leetcode 198.打家劫舍 题目链接:198.打家劫舍 大佬视频讲解&#xff1a;198.打家劫舍视频讲解 个人思路 偷还是不偷&#xff0c;这取决于前一个和前两个房是否被偷了&#xff0c;这种存在依赖关系的题目可以用动态规划解决。 解法 动态规划 动规五部曲&#xff1…