synchronized用法加锁原理

news2024/11/23 17:01:10

目录

  • 使用场景
  • 不同场景加锁对象结论验证实验
    • 实验1: synchronized 修饰方法,加锁对象是类实例,不同实例之间的锁互不影响
    • 实验2: synchronized 加在静态方法上,加锁对象是方法所在类,不同类实例之间相互影响
    • 实验3:synchronized 加在代码块上,加锁对象为括号中对象,而不是所在类实例对象
  • 实现原理
  • Bibliography

使用场景

  1. 修饰方法;
  2. 修饰静态方法
  3. 修饰代码块;
    当修饰普通方法时,加锁对象是方法所在类实例对象;修饰静态方法,加锁对象是方法所在类;修饰代码块,加锁对象是synchronized(objXXX)括号中的对象。为了证明synchronized在不同三种场景中加锁对象总结一致,进行了下面三个验证实验。

不同场景加锁对象结论验证实验

实验1: synchronized 修饰方法,加锁对象是类实例,不同实例之间的锁互不影响

实验步骤: 一个类中定义两个同步方法, 创建类的一个实例对象,开启两个线程,在两个线程中使用同一个对象实例分别调用不同的同步方法。

假设猜想: 线程B由于无法获得实例对象锁而无法进入同步方法2,需要线程A退出方法1之后,线程B才可以进入方法2。

// 同步方法类(所有实验公用)
public class BankAccount{
    String accountName;
    Double balance;

    public BankAccount(String accountName,double balance){
        this.accountName = accountName;
        this.balance = balance;
    }

    public synchronized double deposit(double amount){
        System.out.println(Thread.currentThread().getName()+ " deposit begin");
        balance = balance + amount;
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "deposit end");
        return balance;
    }

    public synchronized double  withdraw(double amount){
        System.out.println(Thread.currentThread().getName() + "withdraw begin");
        balance = balance - amount;
        System.out.println(Thread.currentThread().getName() + "withdraw end");
        return balance;
    }

    public static synchronized void staticMethod(double amount){
        System.out.println(Thread.currentThread().getName()+ "staticDeposit begin");

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "staticDeposit end");

    }

    public void synchronizedCodeBlock(double amount){
        System.out.println(Thread.currentThread().getName()+ "synchronizedCodeBlock1 method begin");
        synchronized (accountName) {
            System.out.println(Thread.currentThread().getName()+ "synchronizedCodeBlock1  begin");
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ "synchronizedCodeBlock1  end");
        }

        System.out.println(Thread.currentThread().getName()+ "synchronizedCodeBlock1 method end");
    }
}
// 自定义线程池(所有实验公用)
public class MyThreadFactory {
    public static ThreadPoolExecutor threadPool() {
       return new ThreadPoolExecutor(5,
               10,
               1,
               TimeUnit.SECONDS,
               new ArrayBlockingQueue<>(985),
                new ThreadPoolExecutor.AbortPolicy());
    }
}
// 测试类
public class SynchronizedUsage {
    public static void main(String[] args) {
        SynchronizedUsage usage = new SynchronizedUsage();
        usage.test1();
    }
   public void test1() {
    BankAccount myBankAccount = new BankAccount("稻草", 0);
    ThreadPoolExecutor executor = MyThreadFactory.threadPool();
    executor.execute(()-> myBankAccount.deposit(100));
    executor.execute(()-> myBankAccount.withdraw(100));
    }
}

实验结果: 线程B尝试获得锁失败,等线程A退出方法1之后才进入了方法2。
在这里插入图片描述
实验分析: 线程A虽然睡眠,但这个过程依然持有对象锁,所以线程B虽然尝试调用实例对象不同的同步方法,但由于对象已经被加锁,所以调用同步方法2失败。

实验2: synchronized 加在静态方法上,加锁对象是方法所在类,不同类实例之间相互影响

实验步骤: 一个类中定义一个静态同步方法,创建两个含有静态同步方法对象实例,开启两个线程,在线程A中,实例1调用静态同步方法,并在方法中睡眠5秒钟,同时实例2也调用静态同步方法。

假设猜想: 线程B由于无法获得类锁而无法进入同步静态方法。

public class SynchronizedUsage {

    public static void main(String[] args) {
        SynchronizedUsage usage = new SynchronizedUsage();
        usage.test2();
    }
    public void test2() {
        BankAccount myBankAccount1 = new BankAccount("稻草", 0);
        BankAccount myBankAccount2 = new BankAccount("稻草", 0);
        executor.execute(()-> myBankAccount1.staticMethod(100));
        executor.execute(()-> myBankAccount2.staticMethod(100));
    }
}

实验结果: 线程B尝试获得锁失败,等线程A退出方法之后才可以。
在这里插入图片描述

实验分析: 线程A虽然睡眠,并且两个线程中使用的不同实例对象,但是线程A是在类上进行加锁,线程B再尝试调用同步方法失败。

实验3:synchronized 加在代码块上,加锁对象为括号中对象,而不是所在类实例对象

实验步骤: 方法中的一个部分代码使用synchronized修饰,对局部变量进行加锁,启动两个线程,在两个线程中使用相同实例调用分别调用两个同步代码块的方法,两个同步。

实验猜想: 两个线程都可以进入方法,在同一时间,只能有一个线程访问同步代码块。

public class SynchronizedUsage {
    public static void main(String[] args) {
        SynchronizedUsage usage = new SynchronizedUsage();
        usage.test3();
    }
 public void test3() {
    
        ThreadPoolExecutor executor = MyThreadFactory.threadPool();
        BankAccount myBankAccount = new BankAccount("稻草", 0);
        executor.execute(()-> myBankAccount.synchronizedCodeBlock(100));
        executor.execute(()-> myBankAccount.synchronizedCodeBlock(100));
    }
}

实验结果: 线程A进入同步代码块之后,线程B只有在线程A退出同步代码块之后才进入了同步代码块。
在这里插入图片描述

实验分析: 由于方法没有设置锁,两个线程都可以进入方法内,线程A进入代码块之后,线程B尝试进入代码块失败,因为线程A已经获得synchronized后括号内对象的锁,线程B只有在线程A退出同步代码块之后,释放对象锁,线程B才可以进入同步代码块,在此之前,线程B只能阻塞等待。

实现原理

  • 同步代码块实现依赖的是JVM的moniterenter和moniterexit两个指令。
  • 同步方法实现依赖的是方法区类元数据中标志位。

被synchronized修饰的方法或者代码块是通过对对象加锁保证并发状态下,共享资源的访问安全,加锁的的实现主要通过monitorenter和monitorexit两个指令实现,经过JVM编译的字节码,会在同步代码块开始和结束分别插入monitorenter和monitorexit,并且每个对象唯一对应一个monitor(存储在对象头的markword中),当线程遇到monitorenter指令,该对象的monitor将被当前线程劫持,对象加锁。monitor的本质是依赖于底层操作系统的Mutex Lock实现,Mutex是操作系统定义信号量的关键字,是操作系统实现同步所使用的技术,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。note:一个synchronized有两个monitorexit,这是保证锁一定可以得到释放,第一个是程序正常执行退出,第二个是程序发生异常,由虚拟机释放。

同步方法是通过对象头和类方法表中access_flags标志位实现代码同步的,被synchronized修饰的方法,对应编译好字节码方法表对应方法修饰access_flags= 1。

Bibliography

  1. 深入分析Synchronized原理(阿里面试题)

  2. Java对象的内存布局

  3. synchronized底层实现原理(保证看懂)

  4. [java] synchronized关键字用法及实现原理详解

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

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

相关文章

docker镜像导入导出

项目背景 1. docker pull 的时候比较慢的情况下&#xff0c; 之前已经下载过的话&#xff0c;可以直接导入使用 2. 有些服务器不允许上外网的情况下&#xff0c;可以先在自己电脑上下载好&#xff0c;再上传到服务器上进行安装 导出镜像 查看镜像 docker images 导出镜像 …

【文件系统】

目录 1 inode 2 软链接 3 硬链接 1 inode 当我们创建一个文件时&#xff0c;用带上 -i 选项可以查看文件的inode: 其中第一个选项就是文件的inode,除此之外另外几列表示的是&#xff1a; 模式 硬链接数 文件所有者 所属组 大小 最后修改时间文件名 ls -l读取存储在磁盘上的文…

windows编译安卓源码记录

环境 Windows10 vmware17 ubuntu22 ubuntu环境设置 装完ubuntu系统后拖拽复制文件进去验证vmtools安装情况&#xff0c;如果vmtools异常很麻烦&#xff0c;试了n多方法&#xff0c;最后还是重新安装系统解决&#xff0c; 如果ok的话&#xff0c;再继续下步骤&#xff0c;否…

【C++入门第四期】类和对象 ( 上 )

前言类的使用类的定义类的两种定义方式&#xff1a;成员变量名的定义建议 类的访问限定符类的作用域类的实列化如何计算类的大小结构体内存对齐规则 this指针this指针的特性 前言 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过…

Linux的目录结构

在Linux世界里&#xff0c;一切皆文件硬件如显卡、cpu等都会映射成一个文件具体的目录结构/bin 是Binary的缩写&#xff0c;这个目录存放着最经常使用的命令 /sbin(/usr/sbin、/usr/local/sbin) s就是Super User的意思&#xff0c;这里存放的是系统管理员使用的系统管理程序 /h…

Elasticsearch扫盲篇

1. 什么是搜索&#xff1f; 在日常的工作和学习中&#xff0c;当我们说想找查询任何的信息的时候&#xff0c;可能第一时间会想到上百度或者谷歌去搜索一下。比如说找一部自己喜欢的电影&#xff0c;或者说找一本喜欢的书&#xff0c;或者找一条感兴趣的新闻。但是百度和谷歌不…

QML基础模型(Basic Model)

最基本的分离数据与显示的方法是使用Repeater元素。它被用于实例化一组元素项&#xff0c;并且很容易与一个用于填充用户界面的定位器相结合。 最基本的实现举例&#xff0c;repeater元素用于实现子元素的标号。每个子元素都拥有一个可以访问的属性index&#xff0c;用于区分不…

第11章 项目人力资源管理

文章目录 项目人力资源管理 过程11.2.1 编制项目人力资源计划的工具与技术 375&#xff08;1&#xff09;层次结构图&#xff08;工作、组织、资源 分解结构&#xff09;&#xff08;2&#xff09;矩阵图&#xff08;责任分配矩阵&#xff0c;RAM&#xff09;&#xff08;3&…

KinectFusion中的ICP算法

投影数据关联-求匹配点 利用算法projective data association对前一帧和当前帧的&#xff08;Vertex、Normal&#xff09;进行匹配&#xff0c;算法如下&#xff1a; 在当前帧 i 的深度图像上的每一个像素 U并行计算&#xff1b;对于深度值大于0的像素&#xff0c;求该像素点…

从破解虫脑到攻克人脑:一条“永生之路”的新赛道?

从破解虫脑到攻克人脑&#xff1a;一条“永生之路”的新赛道&#xff1f; 首张果蝇大脑连接组&#xff1a;耗费十余年&#xff0c;重建三千神经元&#xff0c;超50万突触&#xff01; 论文地址 果蝇幼虫大脑的连接组。 所有脑神经元的形态学都经过了突触分辨率的电子显微镜成像…

聊天机器人开发实战--(微信小程序+SpringCloud+Pytorch+Flask)【后端部分】

文章目录 前言架构SpringCloud服务构建后台搭建Python服务调用 Python算法服务app 总结 前言 趁着五一有时间&#xff0c;先把大三下个学期的期末作业做了&#xff0c;把微信小程序和Java开发的一起做了。顺便把机器学习的也一起做了。所以的话&#xff0c;我们完整项目的技术…

如何用ChatGPT做书籍、报告、文件的读取与互动式问答?故事人物活起来

该场景对应的关键词库&#xff08;15个&#xff09;&#xff1a; 书籍、报告、文件、详细信息、查询、主题、作者、出版日期、出版社、问题、方面、原则、方法、概括、主要观点、解释。 注意&#xff1a; ChatGPT的知识库截止于2021年9月&#xff0c;对于更新的数据或最新出版…

系统化思维:大数中心原理与限制性选择原理。

系统化思维&#xff1a;大数中心原理与限制性选择原理TOC 许多人的思考特点都是混乱而复杂的&#xff0c;只有受过严格训练的人才能做到系统化思维。这里将讨论系统化思维的基础考量。 大数中心原理&#xff1a;大数中心原理是客观而真实的普遍存在&#xff0c;应用在思维上就…

ImageJ实践——拟合矩形选区探究(bounding rectangle),左侧优先法则

在上一篇ImageJ实践中ImageJ实践——测量大小/长短&#xff08;以细胞为例&#xff09;&#xff0c;我勾选了Set Measurements中的Bounding rectangle以测量细胞的长和宽&#xff08;实际上是拟合矩形的长短边&#xff09;&#xff0c;文末我也提出了自己的疑惑&#xff1a;拟合…

【GORM框架】模型定义超详解,确定不来看看?

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: GORM框架学习 近期目标&#xff1a;写好专栏的每一篇文章 目录 一、GORM…

Ansible自动化运维工具---Playbook

Ansible自动化运维工具--playbook 一、playbook1、playbook简介2、playbook应用场景3、yaml基本语法规则4、yaml支持数据结构 二、Inventory中的变量1、inventor变量参数 三、playbook实例1、编写httpd的playbook2、tasks列表和action3、条件测试4、迭代5、with_items模块6、te…

5.4.1树的存储结构 5.4.2树和森林的遍历

回忆一下树的逻辑结构&#xff1a; 双亲表示法&#xff08;顺序存储&#xff09; 如果增加一个结点M&#xff0c;L。毋须按照逻辑上的次序存储。 如果是删除元素&#xff1a; 方案一&#xff1a;比如说删除元素为G,设置其双亲结点为-1。 方案二&#xff1a; 把尾部的结点提上…

真题详解(对象)-软件设计(六十四)

真题详解(DNS)-软件设计&#xff08;六十三)https://blog.csdn.net/ke1ying/article/details/130448106 TCP和UCP都提供了_____能力。 端口寻址 解析&#xff1a; UDP是不可靠无连接协议&#xff0c;没有连接管理&#xff0c;没有流量控制&#xff0c;没有重试。 面向对象…

MySQL 常用命令

#--------------------------- #----cmd命令行连接MySql--------- cd C:\Program Files\MySQL\MySQL Server 5.5\bin # 启动mysql服务器 net start mysql # 关闭mysql服务器 net stop mysql # 进入mysql命令行 mysql -h localhost -u root -p 或mysql -u root -p #---------…