线程加锁关键字

2023-10-27

目录

一.synchronized关键字

1.下面先介绍一下这个关键字的简单用法

2.synchronized的特性

二.volatile关键字


一.synchronized关键字

首先synchronized这个关键字十分重要,一定得了解这个锁

1.下面先介绍一下这个关键字的简单用法

直接修饰普通方法:锁Demo16的对象(直接修饰普通方法,就相当于把锁对象指定为this了)

public class Demo16 {
    public synchronized void method(){
        
    }
}

对静态方法加锁:锁Demo16的类对象(没有this对象)

public class Demo16 {
    public synchronized static void method1(){

    }
}

对代码块加锁:明确指定锁哪个对象(this为锁对象)

public class Demo16 {
    public void method2(){
        synchronized (this){
            
        }
    }
}

锁类对象(通过反射)

public class Demo16 {
    public void method3(){
        synchronized (Demo16.class){
            
        }
    }
}

我们要知道synchronized锁的是什么,其实synchronized的本质操作是修改了Object对象的"对象头"里面的一个标记,当两个线程同时对一个对象加锁,才会发生竞争,否则是不会的!

2.synchronized的特性

(1)互斥

这个是挺好理解的,给一个线程加了锁之后,其他线程就不能再加锁了,只能等待这个线程释放锁之后,其他线程才可以进行加锁

就类似于这样的操作:一个人取钱,门上锁之后,其他人就只能在外面等待(也就是进入阻塞状态) 

注意:上一个线程解锁之后下一个线程并不是直接就获得锁,而是需要操作系统来"唤醒",这也是操作系统线程调度的一部分

例如A,B,C三个线程,线程A先获得锁,然后B尝试获得锁,再C尝试获得锁,此时B和C就会进入阻塞排队等待状态,等A释放锁之后,并不是按来的先后顺序获得锁,而是需要B和C再次竞争去获得锁

(2)刷新内存

synchronized的工作过程:

*获得互斥锁

*从主内存获得变量到工作内存

*执行代码

*将更新后的变量刷新到主内存

*释放互斥锁

其实就和volatile关键字的作用是一样的,可以保证内存可见性问题不会出现,但是要保存内存可见性的话,还是使用volatile好一些,毕竟这个是专门解决这个问题的,而编译器的优化我们也不知道它是在哪加的优化,因此使用标准库提供的volatile关键字更好一些去解决这个问题,下面我会再介绍一下这个关键字

(3)可重入

直观来说,同一个进程针对同一个锁,连续加锁两次,如果出现了死锁,就是不可重入,没有死锁就是可重入

简单解释一下死锁:其实就是一个问题进入到了死循环当中,并且无从解决,例如一个段子:疫情期间最重要的一码通崩溃了,现在程序员需要去公司解决问题,到了门口,门卫需要程序员提供一码通,程序员表示一码通崩溃了我现在就是要去解决一码通的问题,等我修复了,我才能出示一码通,但保安表示你一定要出示一码通,才能进去......然后就陷入死锁当中了,当然这只是一个段子,现在都是可以远程操作解决的,主要还是理解死锁

简单分析一下连续加锁两次会怎么样?

synchronized public void method2(){
        synchronized (this){

        }
    }

外层锁:进入方法就开始加锁,能够加锁成功

里层锁:进入代码块,开始加锁,这次就不能加锁成功了,锁已经被外层占用了,需要等外层解锁之后才能加锁成功

外层锁要执行玩整个方法,才能释放锁,但是要执行完整个方法,里面锁就必须加锁成功才能继续下去......这不就死锁了嘛!!!

针对这样的问题,其实在实际的开发是很容易写出这样的代码的,那岂不是使用锁bug更多了,显然实现JVM的大佬们早已经注意到了这一点,因此就把synchronized实现成了可重入锁,对于可重入锁再进行上述操作就不会出现死锁问题了~

可重入锁内部会记录当前的锁被哪个线程占用,同时也会记录一个"加锁次数",假如对线程A进行第一次加锁的话,会加锁成功,锁内部就会记录当前占用的是线程A,同时加锁次数为1,再次对A进行加锁的话,就不是真正的加锁了,就只把加锁的次数增加一次,后面解锁的时候,先把加锁的次数进行减一,直至次数减到0为止,才会真正进行释放锁!

这样做的意义就降低了程序员的负担(使用成本,提高了开发效率),但是也带来了代价,程序中需要有更高的开销(维护锁属于哪个线程,并且记录加减次数,降低了运行效率)

死锁的其他场景:

上面介绍的是一个线程一把锁,还有其他的场景

*两个线程两把锁:

类似于每个线程占用着一把锁,然后相互请求对方的锁,但两个线程都是竞争,不愿意"妥协",都不愿意释放自己的锁,那么两个线程的请求都得不到回应,就陷入了死锁

*N个线程,M把锁:

这种情况就更复杂一点,这个在书上都有一个经典案例:哲学家就餐问题

要发生上面的死锁,下面四个条件是必不可少的:

①互斥使用:一个锁被一个线程占用,其他线程占用不了(这是锁的本质,保证原子性)

②不可抢占:一个锁被一个线程占用了之后,其他的线程不能把这个锁抢走!

③请求和保持:当一个线程占据了多把锁之后,除非显式的释放锁,否则这些锁始终都是被该线程持有的!

④环路等待:等待关系,成环了~A等B,B等C,C等A,上面三个都是属于锁本身的特性,无法修改,那么要解决死锁问题,就得从最后一点出发了

那么如何避免环路等待呢?

只要约定好,针对多把锁加锁的时候,有固定的顺序就可以了,所有的线程都遵守同样的规则顺序,就不会出现环路等待了!   比如上面的哲学家就餐问题:可以给每根筷子都编个号,然后规定每个哲学家都只能先拿编号较小的筷子,再拿编号大的,然后同时开始拿筷子,第一个哲学家可以拿到一号筷子,第二个可以拿到二号筷子,第三个拿到三号筷子,第四个哲学家拿到四号筷子,然后第五个哲学家拿筷子的时候就会发现一号筷子被占用着,第五个哲学家就会陷入等待中,那么第四号哲学家就可以拿起五号筷子去吃面条了,吃完之后就会释放四号和五号筷子,这样前面的哲学家依次都可以拿到两根筷子,这样就解决了死锁问题了! 

java中也有很多现成的类,有些是线程安全的,有些是线程不安全的,在多线程的情况下,如果使用线程不安全的类的话,就需要注意了:

线程不安全的:

ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder

线程安全的:

Vector(不推荐使用),HashTable(不推荐使用),ConcurrentHashMap,StringBuffer,String

前四个在一些关键的方法上都有synchronized,有了这个操作,就可以保证在多线程环境下,修改一个对象,就没啥问题了,而String是不变对象,无法在多个线程中同时修改String,单个线程也是不可以的!

二.volatile关键字

首先这个关键字的作用是,禁止编译器优化,保证内存可见性,这里会出现这样的问题,主要还是由于计算机的硬件结构所造成的的,计算机想要执行一些计算就需要把内存的数据读到CPU寄存器中,然后在寄存器中计算,在协会到内存中,CPU访问寄存器的速度要比访问内存的速度快很多,当多次访问内存,且结果一致的时候,CPU就会想偷懒了,然后就直接去访问寄存器,不访问内存了,然后如果修改了,写回到内存后,CPU也感知不到,就发生内存可见性了

java圈子里面经常见到的一个java内存模型JMM(java Memory Model),存储更好,JMM就是把上面讲的硬件结构在java中用专门的属于又重新封装了一遍,把CPU称为工作内存(work memory),内存称为主内存(main memory),工作流程,是先把数据从从主内存加载到工作内存,然后工作内存完成计算之后,再写回主内存!这就是JMM模型,那么为什么要这样做呢?一方面因为java作为一个跨平台的编程语言,要把硬件的细节封装起来,期望程序员感知不到CPU、内存等硬件设备,另一方面java社区也经常会造出一些"高大上"的概念,这也是很正常的!!!

另外,在CPU和内存中间其实还有一个缓存器cache,由于CPU从内存读数据,取的太慢了,尤其是频繁的取,就可以吧这样的数据直接放到寄存器中,但是寄存器又是十分的珍贵,其内存也非常小,又划算不来,于是CPU又另外搞了个存储空间,这个空间比寄存器大,比内存小,速度比寄存器慢,但是比内存快,这就是缓存器(cache),而且缓存器也是分等级的

 另外一定要注意volatile不能保证原子性,只能保证可见性,不要和synchronized搞混淆!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程加锁关键字 的相关文章

随机推荐

  • 华为OD机试 -配置文件恢复(C++ & Java & JS & Python)

    描述 有6条配置命令 它们执行的结果分别是 命 令 执 行 reset reset what reset board board fault board add where to add board delete no board at a
  • SPI 及 NOR Flash 介绍

    一 SPI 1 SPI的含义 SPI 串行外设设备接口 Serial Peripheral Interface 是一种高速的 全双工 同步的通信总线 SPI接口主要应用在存储芯片 AD转换器以及LCD中 SPI接口主要应用在存储芯片 AD转
  • Springboot 配置文件中用户名密码加密

    原配置文件内容 详细操作步骤 1 在pom xml文件中加依赖
  • 探究vite——新一代前端开发与构建工具(一)

    Vite 法语意为 快速的 发音 vit 是一种新型前端构建工具 能够显著提升前端开发体验 它主要由两部分组成 一个开发服务器 它基于 原生 ES 模块 提供了 丰富的内建功能 如速度快到惊人的 模块热更新 HMR 一套构建指令 它使用 R
  • 设计模式,命令模式,c++实现,提升内聚性,消除功能类与高层的耦合

    命令模式 给功能类集设置一个接口人 执行者 执行所有需求命令 避免外部 调用者 直接调用某个功能类的内部函数产生大量耦合 用于类间解耦 命令模式是一个高内聚的模式 将一个请求封装为一个对象 使用不同的请求把客户端参数化 对请求排队或者记录请
  • 深度学习中的不确定性:What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision

    转载 https zhuanlan zhihu com p 98756147 原文 What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision NI
  • VUE项目中使用this.$forceUpdate();解决页面v-for中修改item属性值后页面v-if不改变的问题

    页面展示 实现效果 点击实现列表内容的展开 折叠 代码 div class invoice list div class images img src static images invoice pu png img src static
  • SD卡通信接口 SD协议 SPI协议

    SD协议与SPI协议 SD卡虽然只有一种物理接口 但是却支持两种读写协议 SD协议和SPI协议 SPI协议特点 1 SPI协议是单片机中广泛使用的一种通信协议 并不是为SD卡专门发明的 2 SPI协议相对SD协议来说速度比较低 3 SD卡支
  • XGBoost学习(二):介绍及安装

    XGBoost学习 一 原理 XGBoost学习 二 安装及介绍 XGBoost学习 三 模型详解 XGBoost学习 四 实战 XGBoost学习 五 参数调优 XGBoost学习 六 输出特征重要性以及筛选特征 完整代码及其数据 前言
  • 如何使用ssh来连接windows

    什么是SSH协议 在计算机领域中 SSH文本传输协议 安全文件传送协议 是一种数据流连接 提供文件访问 传输和管理功能的网络传输协议 在windows上使用ssh协议因为该协议通过tcp22端口 路由器 服务器 交换机 沙sftp等不安全程
  • Qml中调用C++

    Qml中调用C 方法一 1 写一个C 类 2 在需要使用的地方注册该类 3 qml 中调用 在qml中调用C 类步骤如下 方法一 1 写一个C 类 写一个类 继承自QObject 将类中需要qml调用的方法 用前置的Q INVOKABLE声
  • List集合的定义和原理

    目录 一 List集合的特点介绍 二 List集合的子类 1 ArrayList 2 LinkedList 一 List集合的特点介绍 java util List接口继承于Collection接口 1 List集合是有序的 2 List集
  • VScode C++头文件问题的终极解决办法

    VScode C 头文件问题的终极解决办法 之前在配置VScode环境的时候 按照网上的文章配置 总是找不到头文件 搜索解决方案 都是千篇一律 没有说到重点 在此详细解释一下 局部配置全局配置傻傻分不清楚 网上很多文章都在讲一个配置文件c
  • Java高效并发之乐观锁悲观锁、(互斥同步、非互斥同步)

    乐观锁和悲观锁 首先我们理解下两种不同思路的锁 乐观锁和悲观锁 这两种锁机制 是在多用户环境并发控制的两种所机制 下面看百度百科对乐观锁和悲观锁两种锁机制的定义 乐观锁 Optimistic Locking 相对悲观锁而言 乐观锁机制采取了
  • Driving Behavior Modeling Using Naturalistic Human Driving Data With Inverse Reinforcement Learning

    数学建模 The state s t S mathbf s t in mathcal S st S the driver observes at timestep
  • 20.网络爬虫—Scrapy-Redis分布式爬虫

    网络爬虫 Scrapy redis详讲 Redis的安装与使用 分布式概念和作用 分布式爬虫 分布式爬虫特点 redis的使用 Redis 操作 启动 Redis Desktop Manager下载 特点和架构 安装和使用 Scrapy r
  • RSA算法如何算

    导读 昨天在面试广联达提前批时 面试题中有这么一道选择题 涉及到RSA算法 这个知识点有点模糊 因此在这里做个记录 RSA算法 RSA算法是目前理论和实际应用中最为成熟的和完善的公钥密码体制 RSA用来解决对称密码的密钥分发问题 还可以用来
  • AppStore上架审核燥心事

    项目终于终于通过业务部门的验收允许提交AppStore了 于是当天就提交审核了 就坐等审核通过了 提交后刚好是周末 过完周末回来一查看 审核被拒了 指出有三个方面违反了审核条例 问题1 说是在使用iPad测试的时候 出现网络问题 问题2 说
  • 简易编译器实现(二)使用Bison创建语法分析器

    你也可以通过我的独立博客 www huliujia com 获取本篇文章 简易编译器实现 一 使用Flex创建词法分析器一文介绍了编译器的概念和七个阶段 并说明了如何使用Flex创建词法分析器 本篇文章介绍如何使用Bison创建语法分析器
  • 线程加锁关键字

    目录 一 synchronized关键字 1 下面先介绍一下这个关键字的简单用法 2 synchronized的特性 二 volatile关键字 一 synchronized关键字 首先synchronized这个关键字十分重要 一定得了解