【JUC】十四、synchronized进阶

news/2024/5/20 5:47:36 标签: java, JUC, Monitor

文章目录

  • 1、synchronized
  • 2、synchronized与monitor
  • 3、管程Monitor
  • 4、Q:为什么每个Java对象都可以成为一个锁?
  • 5、小结

1、synchronized

写个demo,具体演示下对象锁与类锁,以及synchronized同步下的几种情况练习分析。demo里有资源类手机Phone,其有三个方法,发短信和发邮件这两个方法有synchronized关键字,另一个普通方法getHello。

java">class Phone {
    public synchronized void sendSMS() throws Exception {
        //TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

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

然后启动两个线程AA和BB,且二者进入就绪状态中间休眠100ms,给AA一个先抢夺CPU时间片的优势。

java">public class Lock8 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        /**
         * start后线程进入的是就绪状态,即具有抢夺CPU时间片(执行权)的能力,并不是直接执行
         * 这里刻意休眠100毫秒,让AA线程先去抢时间片,给AA一个先执行的优势
         */
        Thread.sleep(100);

        new Thread(() -> {
            try {
                phone.sendEmail();
                //phone.getHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();

    }

}

分析以下八种情况的输出结果:

Case1:就上面的代码,直接执行

Case2:sendSMS()方法体加一行TimeUnit.SECONDS.sleep(4),即停留4秒

分析:对于上面两种情况,synchronized出现在示例方法中占的是对象锁,而两线程共用同一个对象,因此先抢时间片的线程AA先执行,而sleep是抱着锁睡,所以输出都是:

java">------sendSMS
------sendEmail

Case3:BB线程改为调用普通方法getHello

分析:getHello方法不用对象锁,所以不用等,而AA线程的sendSMS要sleep4秒,因此getHello就先输出:

java">------getHello
------sendSMS

Case4:两个手机Phone对象,分别给AA和BB线程调用两个synchronized方法

分析:两个Phone对象,两个对象锁,各自调synchronized实例方法,没有抢锁和等待的情况,没有sleep的自然先输出:

java">------sendEmail
------sendSMS

Case5:两个synchronized方法均加static改为静态方法,两线程共用1个Phone资源对象

Case6:两个synchronized方法均加static改为静态方法,两线程分别用2个Phone资源对象

分析:synchronized两个静态方法,锁的就是类锁,即当前类的Class对象,一个类就一把类锁,所以尽管有两个Phone对象在调也没用,先拿到类锁的先执行并输出:

java">------sendSMS
------sendEmail

Case7:BB线程调用静态同步sendEmail、AA线程调用无static的同步方法sendSMS,两线程共用1个Phone资源对象

Case8:BB线程调用静态同步sendEmail、AA线程调用无static的同步方法sendSMS,两线程分别用2个Phone资源对象

分析:不管1个/2个Phone对象,AA线程用的对象锁,BB线程用的类锁,互不影响,对象锁代码中有sleep,晚输出:

java">------sendEmail
------sendSMS

总结:

synchronized实现同步时:

  • synchronized加在静态方法上,锁的就是类锁,即当前类的Class对象,一个类就一把类锁
  • synchronized加在实例方法上,锁的是调用该方法的当前对象,是对象锁,一个类创建100个对象,就有100把对象锁
  • synchronized加在代码块上,锁的是synchronized括号里配置的对象
java">public class LockSyncDemo{

	Object object = new Object();

	public void m1(){
		synchronized (object) {
			System.out.println("同步代码块,锁object对象");
		}
	}

}

在这里插入图片描述

2、synchronized与monitor

写个简单Demo:

java">public class LockSyncDemo {

    Object object = new Object();

    public void m1(){
        synchronized (object){
            System.out.println("hello synchronized!");
        }
    }

    public static void main(String[] args) {

    }
}

编译运行后,在target中open in Terminal

在这里插入图片描述

在终端执行javap 对class文件反编译:

java">javap  -c  ***.class

//-c即反汇编,也可-v,即-verbose,输出更详细的附加信息

部分汇编指令如下:

在这里插入图片描述

上面发现,同步代码块上下出来一对monitor enter和exit,最后还有个未配对的monitor exit,这个类比finally中关资源,是为了万一同步代码块中间发生异常,也确保对象锁被释放。PS:并不总是多一个monitor exit,这个也可在上面Demo代码里的同步代码块中加一句异常:

java">//add
throw new RuntimeException("---exp");

再反编译,就只有一对堆monitor了

在这里插入图片描述

结论:synchronized同步代码块,底层是使用monitorenter和monitorexit指令。

再改为synchronized修饰实例方法:

java">public void m1(){
 	synchronized (object){
        System.out.println("hello synchronized!");
        
    }
}

在这里插入图片描述

结论:JVM读到ACC_SNCHRONIZED标志位,就知道这是一个同步方法。继续改为静态同步方法,再次汇编:

java">public static synchronized void m1() {
    System.out.println("hello synchronized!");
}

在这里插入图片描述

结论:ACC_STATIC,ACC_SYNCHRONIZED访问标志区分该方法是否为静态同步方法

Monitor_227">3、管程Monitor

管程,Monitors,也称监视器,一种结构,结构内的共享资源对工作线程会互斥访问(独占,同一时间,用于保证只有一个线程可以访问被保护的数据或代码)。直白说就是锁。

  • JVM中同步的实现就是基于进入和退出监视器对象Monitor或者是管程对象的。每个对象实例都会有一个Monitor对象,Monitor对象也就是管程。
  • Monitor对象会和Java对象一同创建并销毁,底层由C++实现

比如前面同步方法的底层实现,当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先去持有monitor锁(执行线程就要求先成功持有管程),然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor。

4、Q:为什么每个Java对象都可以成为一个锁?

java">为什么任何一个对象都可以成为或者说带有一个锁(对象锁)?

在Java虚拟机HotSpot中,monitor由ObjectMonitor实现,

在这里插入图片描述

其对应在底层c++就是ObjectMonitor.cpp --> ObjectMonitor.hpp:

在这里插入图片描述

Java任何一个类都有Object这个父类,每个对象实例都会有一个Monitor对象,而Monitor对象的_owner中记录了哪个线程持有了ObjectMonitior对象锁。由此,每一个被锁的对象和Monitor对象关联,Monitor对象中又记录了当前持有锁的线程,当一个monitor对象被某个线程持有后,它便处于锁定状态,因此每个Java对象都可以成为一个锁。

总结:每个对象自带Monitor对象,而Monitor对象在c++里有属性owner,里面记录了当前谁持有锁,由此,每个Java对象都可以成为一个锁。

在这里插入图片描述

再补充下各个属性间的关系:

当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后,进入 _Owner 区域,并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器count加1。

若线程调用 wait() 方法,将释放当前持有的monitor,_owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。

若当前线程正常执行完毕也将释放monitor锁并复位变量的值,以便其他线程进入获取monitor锁。
在这里插入图片描述

5、小结

在这里插入图片描述
在这里插入图片描述


http://www.niftyadmin.cn/n/5223612.html

相关文章

【鸿蒙应用ArkTS开发系列】- 选择图片、文件和拍照功能实现

文章目录 前言创建多媒体Demo工程创建MediaBean 实体类创建MediaHelper工具类API标记弃用问题动态申请多媒体访问权限实现选择图片显示功能打包测试 前言 在使用App的时候,我们经常会在一些社交软件中聊天时发一些图片或者文件之类的多媒体文件,那在鸿蒙…

Linux文件目录结构_文件管理

Linux文件目录结构 Linux目录结构简洁 windows:以多根的方式组织文件 C:\ D:\ E:\ Linux: 以单根的方式组织文件/ Linux目录结构视图 注意区分: 系统管理员:中文“根”,root 系统目录(文件夹):根&#xf…

python3: jieba(“结巴”中文分词库) .2023-11-28

1.安装 jieba库(Windows系统) 打开cmd.exe(命令提示符) ,输入 下面内容后回车, 完成jieba库安装 pip install -i https://mirrors.bfsu.edu.cn/pypi/web/simple jieba 2.例题: 键盘输入一段文本,保存在一个字符串变量txt中,分别用Python内置函数及jie…

【LeetCode】每日一题 2023_11_28 设计前中后队列(数组/链表/双端队列)

文章目录 刷题前唠嗑题目:设计前中后队列题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode?启动!!! 这道题的难度,才是我想象中的中等题的难度好吧,昨天那玩意对我来说还是太难了…

【Vue】绝了!还有不懂生命周期的?

生命周期 Vue.js 组件生命周期: 生命周期函数(钩子)就是给我们提供了一些特定的时刻,让我们可以在这个周期段内加入自己的代码,做一些需要的事情; 生命周期钩子中的this指向是VM 或 组件实例对象 在JS 中,…

python爬虫进阶教程之如何正确的使用cookie

文章目录 前言一、获取cookie二、程序实现三、动态获取cookie四、其他关于Python爬虫技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Pytho…

快速了解Spring AOP的概念及使用

文章目录 1. AOP概念1.1 什么是AOP?1.2 什么是Spring AOP? 2. Spring AOP的使用2.1 引入Spring AOP依赖2.2 编写AOP程序 3. Spring AOP详解3.1 Spring AOP核心概念1. 切点(Pointcut)2. 连接点(Join Point)3…

算法之插入排序及希尔排序(C语言版)

我们来实现上述排序 一.插入排序. 当插入第i(i>1)个元素时,前面的array[0],array[1],.,array[i-1]已经排好序,此时用array[i的排序码与array[i-1]array[i-2].的排序码顺序进行比较,找到插入位置即将arrayU插入,原来位置上的元…