并发编程03:Java锁

news2024/9/24 3:23:47

文章目录

  • 3.1 乐观锁和悲观锁
  • 3.2 通过8种情况演示锁运行案例,看看锁到底是什么
    • 3.2.1 锁相关的8种案例演示code
    • 3.2.2 synchronized有三种应用方式
    • 3.2.3 从字节码角度分析synchronized实现
    • 3.2.4 反编译synchronized锁的是什么
    • 3.2.5 对于Synchronized关键字
  • 3.3 公平锁和非公平锁
    • 3.3.1 何为公平锁/非公平锁
    • 3.3.2 预埋伏AQS
  • 3.4 可重入锁(递归锁)
    • 3.4.1 概念说明
    • 3.4.2 可重入锁种类
  • 3.5 死锁及排查
    • 3.5.1 概念
    • 3.5.2 写一个死锁代码case
    • 3.5.3 如何排查死锁
  • 3.6 写锁(独占锁)/读锁(共享锁)
  • 3.7 自旋锁spinLock
  • 3.8 无锁->独占锁->读写锁->邮戳锁
  • 3.9 无锁->偏向锁->轻量锁->重量锁

3.1 乐观锁和悲观锁

  • 悲观锁: 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改,synchronized和Lock的实现类都是悲观锁,适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再操作同步资源-----狼性锁
  • 乐观锁: 认为自己在使用数据的时候不会有别的线程修改数据或资源,不会添加锁,Java中使用无锁编程来实现,只是在更新的时候去判断,之前有没有别的线程更新了这个数据,如果这个数据没有被更新,当前线程将自己修改的数据成功写入,如果已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如:放弃修改、重试抢锁等等。判断规则有:版本号机制Version,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。-----适合读操作多的场景,不加锁的特性能够使其读操作的性能大幅提升,乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命—佛系锁

3.2 通过8种情况演示锁运行案例,看看锁到底是什么

3.2.1 锁相关的8种案例演示code

class Phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }

    public void hello() {
        System.out.println("------hello");
    }
}

/**
 * 现象描述:
 * 1 标准访问ab两个线程,请问先打印邮件还是短信? --------先邮件,后短信  共用一个对象锁
 * 2. sendEmail钟加入暂停3秒钟,请问先打印邮件还是短信?---------先邮件,后短信  共用一个对象锁
 * 3. 添加一个普通的hello方法,请问先打印普通方法还是邮件? --------先hello,再邮件
 * 4. 有两部手机,请问先打印邮件还是短信? ----先短信后邮件  资源没有争抢,不是同一个对象锁
 * 5. 有两个静态同步方法,一步手机, 请问先打印邮件还是短信?---------先邮件后短信  共用一个类锁
 * 6. 有两个静态同步方法,两部手机, 请问先打印邮件还是短信? ----------先邮件后短信 共用一个类锁
 * 7. 有一个静态同步方法 一个普通同步方法,请问先打印邮件还是短信? ---------先短信后邮件   一个用类锁一个用对象锁
 * 8. 有一个静态同步方法,一个普通同步方法,两部手机,请问先打印邮件还是短信? -------先短信后邮件 一个类锁一个对象锁
 */

public class Lock8Demo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

}

结论:

  • 对于普通同步方法,锁的是当前实例对象,通常指this,所有的同步方法用的都是同一把锁—>实例对象本身
  • 对于静态同步方法,锁的时当前类的Class对象
  • 对于同步方法块,锁的时synchronized括号内的对象

3.2.2 synchronized有三种应用方式

作用于实例方法,当前实例加锁,进入同步代码块前要获得当前实例的锁;
作用于代码块,对括号里配置的对象加锁
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁

3.2.3 从字节码角度分析synchronized实现

javap -c(v附加信息) ***.class 文件反编译

  • synchronized同步代码块
    实现使用的是monitorenter和monitorexit指令
    在这里插入图片描述

  • synchronized普通同步方法
    调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程会将现持有monitor锁,然后再执行该方法,最后在方法完成(无论是否正常结束)时释放monitor
    在这里插入图片描述

  • synchronized静态同步方法
    ACC_STATIC、ACC_SYNCHRONIZED访问标志区分该方法是否是静态同步方法
    在这里插入图片描述

3.2.4 反编译synchronized锁的是什么

面试题:为什么任何一个对象都可以成为一个锁?
C++源码:ObjectMonitor.java—>ObjectMonitor.cpp—>ObjectMonitor.hpp
每个对象天生都带着一个对象监视器,每一个被锁住的对象都会和Monitor关联起来

总结:指针指向Monitor对象(也称为管程或监视器)的真实地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现):
在这里插入图片描述
在这里插入图片描述

3.2.5 对于Synchronized关键字

后面章节详说

3.3 公平锁和非公平锁

3.3.1 何为公平锁/非公平锁

  • 公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似于排队买票,先来的人先买,后来的人再队尾排着,这是公平的----- Lock lock = new ReentrantLock(true)—表示公平锁,先来先得。
  • 非公平锁:是指多个线程获取锁的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级反转或者饥饿的状态(某个线程一直得不到锁)---- Lock lock = new ReentrantLock(false)—表示非公平锁,后来的也可能先获得锁,默认为非公平锁

面试题:

  • 为什么会有公平锁/非公平锁的设计?为什么默认非公平?
    恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分地利用CPU的时间片,尽量减少CPU空间状态时间。
    使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得很大,所以就减少了线程的开销。
  • 什么时候用公平?什么时候用非公平?
    如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省了很多线程切换的时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。

3.3.2 预埋伏AQS

后续深入分析

3.4 可重入锁(递归锁)

3.4.1 概念说明

是指在同一线程在外层方法获取到锁的时侯,在进入该线程的内层方法会自动获取锁(前提,锁对象的是同一个对象),不会因为之前已经获取过还没释放而阻塞---------优点之一就是可一定程度避免死锁。

3.4.2 可重入锁种类

  • 隐式锁(即synchronized关键字使用的锁),默认是可重入锁
    在一个synchronized修饰的方法或者代码块的内部调用本类的其他synchronized修饰的方法或者代码块时,是永远可以得到锁
  • 显式锁(即Lock)也有ReentrantLock这样的可重入锁
public class ReEntryLockDemo {

    public static void main(String[] args) {
        final Object o = new Object();
        /**
         * ---------------外层调用
         * ---------------中层调用
         * ---------------内层调用
         */
        new Thread(() -> {
            synchronized (o) {
                System.out.println("---------------外层调用");
                synchronized (o) {
                    System.out.println("---------------中层调用");
                    synchronized (o) {
                        System.out.println("---------------内层调用");
                    }
                }
            }
        }, "t1").start();

        /**
         * 注意:加锁几次就需要解锁几次
         * ---------------外层调用
         * ---------------中层调用
         * ---------------内层调用
         */
        Lock lock = new ReentrantLock();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("---------------外层调用");
                lock.lock();
                try {
                    System.out.println("---------------中层调用");
                    lock.lock();
                    try {
                        System.out.println("---------------内层调用");
                    } finally {
                        lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

3.5 死锁及排查

3.5.1 概念

死锁是指两个或两个以上的线程在执行过程中,因抢夺资源而造成的一种互相等待的现象,若无外力干涉,则它们无法再继续推进下去。

产生原因:

  • 系统资源不足
  • 进程运行推进顺序不合适
  • 系统资源分配不当
    在这里插入图片描述

3.5.2 写一个死锁代码case

public class DeadLockDemo {
    static  Object a=new Object();
    static  Object b=new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (a){
                System.out.println("t1线程持有a锁,试图获取b锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println("t1线程获取到b锁");
                }
            }
         },"t1").start();

        new Thread(() -> {
            synchronized (b){
                System.out.println("t2线程持有a锁,试图获取a锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println("t2线程获取到a锁");
                }
            }
        },"t2").start();
    }
}

3.5.3 如何排查死锁

  • 纯命令
    • jps -l
    • jstack 进程编号
  • 图形化
    • jconsole

3.6 写锁(独占锁)/读锁(共享锁)

深度源码分析见后面

3.7 自旋锁spinLock

深度源码分析见后面

3.8 无锁->独占锁->读写锁->邮戳锁

深度源码分析见后面

3.9 无锁->偏向锁->轻量锁->重量锁

深度源码分析见后面

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

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

相关文章

创维高安版-E900-Hi3798MV100-强刷卡刷固件包-当贝纯净桌面

创维高安版-E900-Hi3798MV100-强刷卡刷固件包-当贝纯净桌面-内有主板图短接点和教程 特点: 1、适用于对应型号的电视盒子刷机; 2、开放原厂固件屏蔽的市场安装和u盘安装apk; 3、修改dns,三网通用; 4、大量精简内置…

关于百度地图开放平台api覆盖物“自定义Marker图标”不能正常显示的解决方案

“自定义Marker图标”不能正常显示(用网上图片能正常显示,用本地图片就不能显示), 分两种情况: 1.网上图片,往往是图片的url地址有问题,也可能是url地址的图片失效了。 2.本地图片,这…

linux软件包管理和进程管理

目录标题 RPM管理工具rpm安装rpm查询功能rpm软件包升级、卸载 YUM管理工具建立本地光盘源配置互联网源yum工具管理软件包 ps指令动态查看进程top RPM管理工具 软件包(本地–网络)—安装(软件包)—卸载(软件&#xff0…

【Python】【进阶篇】24、Django if标签详解

目录 24、Django if标签详解1. 模板标签1) 判断逻辑的 if 标签 24、Django if标签详解 本节继续讲解 Django 的模板语言,Django 内置了许多标签用于简化模板的开发过程,同时 Django 也支持自定义标签,这极大的方便了 Web开发者,下…

拓展系统变量

文章目录 拓展系统变量 使用方式拓展系统变量获取服务端IP - $ZSERVERIP获取客户端IP - $ZCLIENTIP获取最大许可数量 - $ZMAXLICENSE获取当前系统名称 - $ZOSNAME获取字符串最大长度 - $ZMAXSTRINGLEN获取当前登录用户ID - $ZUSERID获取当前登录用户名 - $ZUSERNAME系统最近错误…

【Linux从入门到精通】C语言模拟实现进度条小程序

在Linux下,我们安装软件时会经常看到进度条,来告知我们安装的进度。我们不妨自己模拟实现一个进度条,看看其中的细节。模拟实现进度条并不困难,但其中的细节我们又不可忽视。本篇文章会对模拟实现进度条进行详解。 文章目录 一、进…

顺序表(数据结构)---排队啦!

目录 前言: 1.线性表的性质 2.静态数组or动态数组 2.1静态数组 2.2动态数组 3.结构体的创建 4*接口函数的详细讲解 4.1初始化结构体 4.2尾插 4.3打印数据 4.4用完后销毁创建的堆空间 4.5 尾删 4.6头插 4.7头删 4.8查找 4.9任意位置插入 4.10任意位…

springboot+jsp商务安全邮箱(源码+文档)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot商务安全邮箱。项目源码以及部署相关请联系风歌,文末附上联系信息 。 💕💕作者:风歌&…

谈谈Edge浏览器新出的分屏功能

谈谈Edge浏览器新出的分屏功能 前言 在 2023 年三月份微软为 Microsoft Edge 浏览器的稳定版本带来了一个新功能 —— 分屏浏览 (Split Screen),此功能允许用户在当前页面以左右视图的形式并排打开两个标签页面,作用上类似于应用的分屏可以让浏览器同时处…

Kali Linux部署qemu虚拟化启动img镜像文件

一、先下载最新版本的Kali环境 Kali Linux官网下载网址:Get Kali | Kali Linux 安装到VMware里面后,调整内存大小为4G(如果自己电脑内存32G的话,可以调整为8G) 更新一下Kali Linux源 然后安装如下软件 apt install qe…

二十九、交换机堆叠与集群

文章目录 堆叠技术概述一、可靠组网二、堆叠技术名称三、华为堆叠原理1、基本概念2、堆叠端口:(逻辑端口)3、堆叠拓扑类型4、堆叠硬件要求 四、堆叠配置示例(华三模拟器)1、sw1:2、sw2:3、激活i…

弹射起步——pythonweb开发Flask框架,前端原生+Flask后端框架+mysql数据库实战(附带小案例)

大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

HCT:深度是我们没有的奢侈品

文章目录 Deep is a Luxury We Don’t Have摘要本文方法Efficient AttentionThe HCT Architecture Deep is a Luxury We Don’t Have 摘要 医学图像具有高分辨率。高分辨率对于早期发现恶性组织至关重要。然而,这一解决方案在建模长期依赖性方面提出了挑战。浅层t…

接口自动化测试的神器:使用Python编写高效的自动化测试工具

B站首推!2023最详细自动化测试合集,小白皆可掌握,让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 摘要: 安装工具: 测试脚本 编写python脚本 1.使用requests发送HTTP请求 2.使用py…

生产环境出现CPU占用过高,请谈谈你的分析思路和定位

假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位 记一次印象深刻的故障? 结合Linux 和 JDK命令一起分析,步骤如下 使用top命令找出CPU占比最高的 ps -ef 或者 jps 进一步定位,得知是一个怎么样的后台程序出的问题 定位…

夏驰和徐策的解决数学问题思路——反证法

反证法是一种证明方法,它的基本思路是通过假设某个结论不成立,然后构造出一个矛盾的情况来推导出原先假设的结论是成立的。 具体来说,反证法一般包含以下步骤: 1. 假设所要证明的命题不成立。 2. 通过这个假设,构造…

网易云音乐开发--个人中心页效果实现

内网穿透 就是我们真机调试,是没有数据的 就是我们手机上去访问我们电脑上自己搭的服务器,肯定是访问不到的 此时就需要我们内网穿透 1.winR 输入 cmd 输入ipconfig 2.找到无线局域网适配器的IPv4 3.重新设置一个新的地址,只需将host中…

Snipaste介绍、安装、使用技巧(截图贴图工具)

一、简介 Snipaste 是一个简单但强大的截图贴图工具,也可以让你将截图贴回到屏幕上!下载并打开 Snipaste,按下 F1 来开始截图,再按 F3,截图就在桌面置顶显示了。就这么简单! 你还可以将剪贴板里的文字…

HTMLCxx 编译说明

1、编译库 下载htmlcxx之后,打开项目编译工程: 双击编译之后,会出现错误: 此时,双击定位到错误的位置: 去掉双引号,重新输入 "",编译通过 2、引用库解析数据 这时候会定位到当前的错…

双目测距--3 双目标定

目录 -1 流程说明: 0 几个重要 函数 1、calibrateCamera()函数 2、stereoCalibrate() 3、findChessboardCorners() 棋盘格角点检测 4、stereoRectify() 5、initUndistortRectifyMap() 6、remap() 1、用于标定的图像 2、标定前 3、OpenCV进行双目标定 单…