JavaEE初阶Day 13:多线程(11)

目录

  • Day 13:多线程(11)
    • 常见的锁策略
      • 1. 悲观锁 vs 乐观锁
      • 2. 重量级锁 vs 轻量级锁
      • 3. 自旋锁 vs 挂起等待锁
      • 4. 可重入锁 vs 不可重入锁
      • 5. 公平锁 vs 非公平锁
      • 6. 互斥锁 vs 读写锁
    • synchronized实现原理
      • 1. 锁升级
      • 2. 锁消除
      • 3. 锁粗化
    • CAS

Day 13:多线程(11)

常见的锁策略

锁策略可以理解为,这把锁在加锁/解锁/遇到锁冲突的时候都会怎么做

并非局限于Java中,其他编程语言,其他的系统级别的组件,但凡涉及到锁,都和锁策略有关系

1. 悲观锁 vs 乐观锁

加锁的时候,预测当前锁冲突的概率是大还是小

  • 预测当前锁冲突概率大,后续要做的工作往往就会更多,加锁的开销就更大(时间、系统资源),此时采用悲观锁
  • 预测当前锁冲突概率小,后续要做的工作往往就会更少,加锁的开销就更小(时间、系统资源),此时采用乐观锁

Java中的synchronized既是乐观锁也是悲观锁,支持自适应,能够自动的统计出当前锁冲突的次数,进行判定当前锁冲突的概率高低

  • 当冲突概率低的时候,按照乐观锁的方式来执行(速度更快)
  • 当冲突概率高的时候,升级为悲观锁的方式执行(做的工作更多)

悲观锁往往是要通过内核来完成一些操作的,要做的工作就多

乐观锁往往是纯用户态的一些操作,要做的工作就少

2. 重量级锁 vs 轻量级锁

一般来说,悲观锁往往就是重量级锁;乐观锁往往就是轻量级锁

  • 加锁过程做的事情多,重量
  • 加锁过程做的事情少,轻量

3. 自旋锁 vs 挂起等待锁

  • 自旋锁是轻量级锁的一种典型实现方式
//伪代码
void lock() {while(true) {if(锁是否被占用) {continue;}获取到锁break;}
}

cpu在空转忙等,消耗了更多的CPU资源,但是一旦锁被释放,就能第一时间拿到锁

  • 挂起等待锁是重量级锁的一种典型实现方式

    • 借助系统中的线程调度机制,当尝试加锁,并且锁被占用了,出现锁冲突,就会让当前这个尝试加锁的线程被挂起(阻塞状态)
    • 此时这个线程就不会参与调度了,直到这个锁被释放,然后系统才能唤醒这个线程,去尝试重新获取锁,拿到锁的速度更慢,节省CPU,消耗的时间更长,一旦线程被阻塞了,什么时候被唤醒,这个过程是不可控的

synchronized轻量级锁部分,基于自旋锁实现;重量级锁部分,基于挂起等待锁实现

4. 可重入锁 vs 不可重入锁

  • Java中的synchronized是可重入锁,一个线程针对同一把锁连续加锁两次,不会死锁

  • C++中的std::mutex是不可重入锁,一个线程针对同一把锁连续加锁两次,会出现死锁

5. 公平锁 vs 非公平锁

  • 公平锁:严格按照先来后到的顺序来获取锁,哪个线程等待的时间长,哪个线程就拿到锁
  • 非公平锁:若干个线程,各凭本事,随机的获取到锁,和线程等待时间就无关了

synchronized属于非公平锁,多个线程尝试获取到这个锁,此时是按照概率均等的方式进行获取

系统本身线程调度的顺序就是随机的,如果需要实现公平锁,就需要引入额外的队列,按照加锁顺序,把这些获取锁的线程入队列,再一个一个出队列

6. 互斥锁 vs 读写锁

  • 互斥锁:一个线程获取到锁并进行加锁,另一个线程就不能对其加锁了

  • 读写锁:多个线程读同一个变量,不会有线程安全问题

    • 读锁和读锁之间,不会产生互斥
    • 写锁和写锁之间,会产生互斥
    • 读锁和写锁之间,会产生互斥

    突出体现的是读操作和读操作之间是共享的,不会互斥的,有利于降低锁冲突的概率,提高并发能力

日常开发中,有很多场景,属于**”读多,写少“**,大部分操作都是读,偶尔有写的操作

  • 如果使用普通的互斥锁,此时,每次读操作之间都会互斥,影响效率

  • 如果使用读写锁,就能够有效的降低锁冲突的概率,提高效率

Java标准库/操作系统api也提供了读写锁的实现

synchronized实现原理

synchronized既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁,轻量级锁是自旋锁实现,重量级锁是挂起等待锁实现,是可重入锁,不是读写锁,是非公平锁

那么synchronized如何”自适应“

1. 锁升级

锁升级的过程:

在这里插入图片描述

偏向锁

  • 首次使用synchronized对对象进行加锁的时候,不是真正的加锁,而只是做一个”标记“,非常轻量非常快,几乎没有开销
  • 如果没有别的线程尝试对这个对象加锁,就可以保持这个状态,一直到解锁,解锁也就是修改一下上述标记,几乎没有开销,前述过程就相当于没有任何加锁操作,速度非常快
  • 但是,如果在偏向锁状态下,有某个线程也尝试来对这个对象加锁,立马把偏向锁升级为轻量级锁,实现真正的加锁

上述的升级过程,针对一个锁对象来说,是不可逆的,只能升级不能降级,一旦升级到重量级锁,不会回退到轻量级锁

2. 锁消除

锁消除是一种编译器优化策略

代码中写了加锁操作,编译器和JVM会对当前的代码做出判定,看这个地方是否真的需要加锁,如果不需要加锁,就会自动把加锁操作给优化掉

最典型的就是:在只有一个线程里,使用synchronized

由于编译器优化,需要保证优化后的逻辑和优化前要等价,这里做的是比较保守的,能够起到的作用有限,与之前谈到的偏向锁互不相干,也不冲突

3. 锁粗化

锁的粒度:加锁的范围内,包含多少代码,代码越多,就认为锁的粒度越粗,反之越细

锁粗化:一种优化策略,有些逻辑中,需要频繁加锁解锁,编译器就会自动的把多次细粒度的锁,合并成一次粗粒度的锁

例如:领导安排了三个工作

  • 分三次给领导打电话会把每个工作
  • 一次电话,汇报三个工作

CAS

CAS:compare and swap(比较和交换),这是一条CPU指令,就可以完成比较和交换这一套操作

可以将CAS的流程想象成一个方法

boolean cas(address, reg1, reg2){if(*address == reg1){把address内存地址的值和reg2寄存器的值进行交换return true;}return false;
}

这里说的交换,实际更多的是用来赋值,一般更关心内存中交换后的数据,而不关心reg2寄存器交换后的数据,可以近似认为上述操作把reg2的值赋值给内存中

  • 由于CPU提供了上述指令,因此操作系统内核,也就能够完成上述操作,就会提供出这样的CAS的api,JVM又对于系统的CAS的api进一步封装了,在Java代码中就可以使用CAS操作了

  • 但是实际上,CAS被封装到了一个unsafe包中,容易出错,不鼓励大家直接使用CAS

Java中也有一些类,对CAS进行了进一步的封装,典型的就是原子类

例如java.util.comcurrent.atomia中的AtomicInteger,相当于针对int进行了封装,可以保证此处的++或–操作,是原子的

Java中不支持运算符重载,无法针对原子类进行++、–;C++和python能够支持运算符重载,可以重新定义±*/等各种运算符的作用

package thread;import java.util.concurrent.atomic.AtomicInteger;public class Demo38 {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{//count++count.getAndIncrement();//++countcount.incrementAndGet();//count--count.getAndDecrement();//--countcount.decrementAndGet();//count+=10count.getAndAdd(10);});t1.start();t1.join();System.out.println("count = " + count);}
}

此处我们的代码中,没有用到任何加锁操作,使得代码以更高的效率来执行程序

这一套基于CAS不加锁来实现线程安全代码的方式,也成为无锁编程

  • 这一套操作适用范围没有加锁更广泛,针对一些特殊场景,使用CAS是更高效的,但是有些场景,不太适合使用CAS
  • 一种更加折中的办法,可以基于CAS来封装成自旋锁(自旋锁也是基于CAS来实现的),这样做其实也就失去了“无锁编程”的意义了

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

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

相关文章

自助棋牌室如何用一招留住80%的客户?

棋牌室如何用一招守住80%的回头客,你想知道吗? 记得收藏,希望对你有一点点帮助! 今天我就分享一个非常简单的方法,简单到所有的棋牌室老板你立马就可以去执行的方法!第一步,加好友,…

Java面试八股之Iterator接口和Iterable接口

1. Java为什么不直接实现Iterator接口,而是实现Iterable? 这道题算是一道比较基础的题,面试官肯定也不是想让回答得多深入,只是考查对迭代器的了解程度,最好是看过源码,实际上迭代器的源码并不难。我们把注释折叠起来…

LIUNX文件系统

目录 1.磁盘的物理结构 2.CHS定位法 3.磁盘的逻辑存储 4.系统层面 inode.block[15] 创建文件的流程 查找文件的流程 了解文件系统,首先要了解磁盘是如何存储和读取数据的。 1.磁盘的物理结构 可以理解这个盘上有很多的小磁铁,通过旋转盘面和摆动…

葡韵饼店:云上清明节,千里寄哀思

清明,又称踏青节、祭祖节、行清节,节期在仲春与暮春之交,源自于上古时代的祖先信仰和春祭礼俗,兼具人文与自然两大内涵。 每当到了这个时候,人们都会携带祭祀物品,与家人们齐聚结伴,登山祭祖&am…

【Python】用python实现编译脚本

这个脚本可以自动扫描目录下的.c和.s文件并编译,同时生成hex和bin文件 ,可以代替Makefile工作。cortex-m 单片机 # -*- coding: gbk -*-import os import sys import time修改编译脚本之后需要全编译一次# CC gcc CC C:\\ARM_GCC\\bin\\arm-none-eabi-…

支付宝支付之SpringBoot整合支付宝创建自定义支付二维码

文章目录 自定义支付二维码pom.xmlapplication.yml自定义二维码类AlipayService.javaAlipayServiceImpl.javaAlipayController.javaqrCode.html 自定义支付二维码 继&#xff1a;SpringBoot支付入门 pom.xml <dependency><groupId>org.springframework.boot<…

PHP中常见的@注释的含义

api: 提供给第三方使用的接口 author: 标明作者 param: 参数 return: 返回值 todo: 待办 version: 版本号 inheritdoc: 文档继承 property: 类属性 property-read: 只读属性 property-write: 只写属性 const: 常量 deprecated: 过期方法 example: 示例 final: 标识类是终态, 禁…

前端开发该不该“跳槽”到鸿蒙?

前言 面对互联网行业的激烈竞争&#xff0c;许多人都深感2023年已是不易&#xff0c;而展望2024年&#xff0c;似乎更是难上加难。这一切的根源&#xff0c;皆因行业多年发展后&#xff0c;人才市场的饱和现象愈发严重。那么&#xff0c;作为前端开发者&#xff0c;我们究竟该…

速看!2024年强基计划报考流程及常见问答

01什么是强基计划&#xff1f; 为加强基础学科拔尖创新人才选拔培养&#xff0c;教育部在深入调研、总结高校自主招生和上海等地高考综合改革试点经验的基础上&#xff0c;制定出台了《关于在部分高校开展基础学科招生改革试点工作的意见》&#xff08;也称“强基计划”&#…

SpringBoot启动加载自己的策略类到容器中使用?

使用InitializingBean接口 springboot中在启动的会自动把所有的实现同一个接口的类&#xff0c;都会转配到标注Autowired的list里面 而且实现了InitializingBean接口&#xff0c;在启动的赋值的时候&#xff0c;我们会把所有的策略类&#xff0c;重放到map中&#xff0c;我们在…

c++ 11 添加功能 变量类型推导

1.概要 变量类型推导 2.代码 #include <iostream> #include <map> using namespace std; int main() { std::map<std::string, std::string> m{ {"a", "apple"}, {"b","banana"} }; // 使用迭代器遍历…

发布订阅模式以及mitt源码实现

发布订阅模式以及mitt源码实现 前言&#xff1a;我为什么要写他&#xff1f; 场景1: 我在写一个组件&#xff0c;但是层层传递之后&#xff0c;全是属性/事件的传递。中间有很多缘由&#xff0c;vuex 又不适合&#xff0c;最后选择了eventBus&#xff0c;但是vue3 已经不再提供…

【尚硅谷】Git与GitLab的企业实战 学习笔记

目录 第1章 Git概述 1. 何为版本控制 2. 为什么需要版本控制 3. 版本控制工具 4. Git简史 5. Git工作机制 6. Git和代码托管中心 第2章 Git安装 第3章 Git常用命令 1. 设置用户签名 1.1 基本语法 1.2 案例实操 2. 初始化本地库 2.1 基本语法 2.2 案例实操 3. 查…

【运输层】TCP 的流量控制和拥塞控制

目录 1、流量控制 2、TCP 的拥塞控制 &#xff08;1&#xff09;拥塞控制的原理 &#xff08;2&#xff09;拥塞控制的具体方法 1、流量控制 一般说来&#xff0c;我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快&#xff0c;接收方就可能来不及接收&#x…

milvus服务安装bash脚本指令理解

下拉镜像&#xff1a;docker pull milvusdb/milvus:v2.4.0-rc.1下载文件&#xff1a;https://hub.yzuu.cf/milvus-io/milvus/blob/master/scripts/standalone_embed.sh安装启动&#xff1a;bash standalone_embed.sh start详细解释下这段代码&#xff1a;wait_for_milvus_runni…

伪代码——基础语法入门

1、简介 伪代码是一种用来描述算法或程序逻辑的抽象化编码方式&#xff0c;它不依赖于任何特定的编程语言语法&#xff0c;而是使用类似自然语言的形式来描述算法步骤。通常用于算法设计、教学和沟通&#xff0c;伪代码可以更直观地表达问题的解决方案&#xff0c;而不必受限于…

Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机

Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机 文章目录 Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机1.安装virtualbox2.下载Window.iso文件并载入3.问题解决3.1 Kernel driver not installed (rc-1908)3.2 VT-x is disabled in the BIOS for all CPU modes 4.安装Wi…

【python】python 模块学习之--Fabric

基础一&#xff1a; #!/usr/bin/env pythonfrom fabric.api import *env.userrootenv.hosts[218.78.186.162,125.208.12.56]env.passwords{ root218.78.186.162:22:XXX,root125.208.12.56:22:XXXX0}runs_once ####runs_once代表只执行一次def local_tas…

在开源框架使用自有数据集的方法-以增量学习工具箱PyCIL为例

回答多位朋友提出的&#xff0c;如何在开源框架使用自有数据集。思路是理解开源代码的设计方法&#xff0c;根据其设计方法增加相应的代码。 具体方法如下&#xff1a; 1.查看开源代码提供者的说明 https://github.com/G-U-N/PyCIL#datasets&#xff0c;这里提供了入手的起点…

带你实现一个github注册页面的星空顶

带你实现一个github注册页面的星空顶 github的注册页面可以说是非常的好看&#xff0c;如果没有看过的可以看下面的图片&#xff1a; 那么要如何实现下面的这个效果呢&#xff1f; 首先我们研究一下他的这个官网 首先我看到的后面的这个背景&#xff0c;是不是一个纯色的背景…