八锁现象——synchronized关键字实战详解

news/2024/11/18 3:27:01/

八锁现象

目录

    • 八锁现象
    • 哪八种锁?
    • 八锁第一锁和第二锁
      • 第一锁
      • 第二锁
      • 第一、二锁解释
    • 八锁第三锁
      • 第三锁解释
    • 八锁第四锁
      • 第四锁解释
    • 八锁第五锁
      • 第五锁解释
    • 八锁第六锁
      • 第六锁解释
    • 八锁第七锁
      • 第七锁解释
    • 八锁第八锁
      • 第八锁解释
        • 八锁总结:

羡慕案例中使用sleep方法制造延迟来测试。

学习目标: 什么是锁?锁是什么?如何判断锁的是谁

哪八种锁?

1、一个对象,俩个同步方法

2、一个对象,俩个同步方法,一个方法延迟

3、两个对象,两个同步方法

4、一个对象,一个同步,一个普通方法

5、一个对象,俩个静态同步方法

6、两个对象,俩个静态同步方法

7、一个对象,一个静态的同步方法,一个同步方法

8、两个对象,一个静态的同步方法,一个同步方法

八锁第一锁和第二锁

第一锁

现象演示

两个线程调用Phone执行Phone下的发短信和打电话方法

1、标准情况下,两个线程先执行发短信,再打电话。

结果:发短信先执行

import java.util.concurrent.TimeUnit;/*** 八锁,就是关于锁的八个问题* 1、标准情况下,两个线程先执行发短信,再打电话*/
public class Test1 {public static void main(String[] args) {Phone phone = new Phone();// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{phone.call();},"B").start();}
}
class Phone{// 发短信public synchronized void sendSms(){System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}
}

第二锁

现象演示

2、sendSms方法延时4秒,仍然是先执行发短信,再打电话。所以我们排除执行顺序是按照线程创建的先后顺序

结果:发短信先执行

import java.util.concurrent.TimeUnit;/*** 八锁,就是关于锁的八个问题* 2、sendSms方法延时4秒,仍然是先执行发短信,再打电话*/
public class Test1 {public static void main(String[] args) {Phone phone = new Phone();// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{phone.call();},"B").start();}
}
class Phone{// 发短信public synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}
}

第一、二锁解释

被synchronized修饰的方法,锁的对象是方法的调用者,此方法中均是对象Phone来调用的,即两个方法调用的对象是同一个、使用的同一把锁,谁先拿到就谁先执行。

八锁第三锁

现象:phone、phone1两个不同对象在两个线程A、B里分别调用发短信、打电话,其中发短信方法有4秒休眠。

结果:打电话先执行

import java.util.concurrent.TimeUnit;/**
也可以使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用*/
public class Test2 {public static void main(String[] args) {Phone2 phone = new Phone2();Phone2 phone1 = new Phone2();/*** 被synchronized 修饰的方式和普通方法 先执行sendSms() 还是 hello()* 答案: hello()*  解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!*/// 线程A调用phone发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B使用phone1调用打电话new Thread(()->{phone1.call();},"B").start();}
}class Phone2{// 发短信public synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}
}

第三锁解释

使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用

八锁第四锁

现象:phone对象在线程A中调用被synchronized修饰的发短信方法 和 在线程B中调用未被synchronized修饰的普通方法hello

结果:hello先执行

import java.util.concurrent.TimeUnit;/*** 被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()* 答案: hello()*  解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!*/
public class Test2 {public static void main(String[] args) {Phone2 phone = new Phone2();s// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B调用未被synchronized修饰的方法hello()new Thread(()->{phone.hello();},"B").start();}
}class Phone2{// 发短信public synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}// say hello 没有被锁 也就是说不是同步方法、不受锁的影响,线程会直接把它来执行public void hello(){System.out.println("hello");}
}

第四锁解释

被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()?
答案是hello()解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!

八锁第五锁

现象:Phone3在线程A 和 线程B中分别调用Phone3的被synchronized和static修饰的静态同步方法 发短信 和 打电话。

结果:发信息先调用

import java.util.concurrent.TimeUnit;/*** 将两个同步方法设置为静态*/
public class Test3 {public static void main(String[] args) {// 线程A发短信new Thread(()->{Phone3.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{Phone3.call();},"B").start();}
}class Phone3{/*** 在此处我们将 两个同步方法均设为静态方法,此两个方法在类加载是被加载* 所以,在此2个同步方法加载时,它们被全局唯一的Phone3.class对象加载,因为此Phone3.class全局唯一,故而他们被同一个对象加载* 所以此时仍然是发短信先输出*/// 发短信public static synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public static synchronized void call(){System.out.println("打电话");}
}

第五锁解释

只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!所以说这里是同一个锁,并不是因为synchronized  这里程序会从上往下依次执行

八锁第六锁

现象:phone、phone1两个对象来分别调用两个静态同步方法,哪个先执行?

结论:发短信先执行

import java.util.concurrent.TimeUnit;/***/
public class Test3 {public static void main(String[] args) {// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别Phone3 phone = new Phone3();Phone3 phone1 = new Phone3();// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{phone1.call();},"B").start();}
}class Phone3{// 发短信public static synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public static synchronized void call(){System.out.println("打电话");}
}

第六锁解释

/*** 同被static+synchronized 修饰的两个方法,是先发短信还是先打电话()?*  答案:发短信*  解释:只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一,class模板只有一个,即使是phone和phone1不是同以次实例化的对象,但是无论 phone.静态方法() 还是 phone1.静态方法() 它们均相当于Phone.静态方法()*  所以说这里是同一个锁,并不是因为synchronized导致。*/

八锁第七锁

现象:被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?

结论:先打电话

import java.util.concurrent.TimeUnit;/***/
public class Test3 {public static void main(String[] args) {// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别Phone3 phone = new Phone3();// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{phone.call();},"B").start();}
}class Phone3{// 发短信public static synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}
}

第七锁解释

/*** 被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?* 答案:打电话* 解释:只要被static修饰锁的是class模板, 而synchronized 锁的是调用的对象* 这里是两个锁互不影响,按时间先后执行*/

八锁第八锁

现象:phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,哪个先执行?

结论:随机执行

import java.util.concurrent.TimeUnit;/***/
public class Test3 {public static void main(String[] args) {// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别Phone3 phone = new Phone3();Phone3 phone1 = new Phone3();// 线程A发短信new Thread(()->{phone.sendSms();},"A").start();// 休息一秒try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 线程B打电话new Thread(()->{phone1.call();},"B").start();}
}class Phone3{// 发短信public static synchronized void sendSms(){// 休息四秒try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}// 打电话public synchronized void call(){System.out.println("打电话");}
}

第八锁解释

phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,先执行发短信还是打电话?
答案:打电话
解释: 只要被static 修饰的锁的就是整个class模板
这里一个锁的是class模板 一个锁的是调用者 
所以锁的是两个对象,互不影响,按CPU调度顺序执行

八锁总结:

synchronized(Demo.class){
}
和
synchronized(this){
}

1、new this 调用的是这个对象,是一个具体的对象!
2、static class 唯一的一个模板,即Class对象!

synchronized 锁的对象是方法的调用者

  • 普通同步方法的调用者是类的实例(实例化对象)
  • 静态同步方法的调用者是类的对象(class对象)

在我们编写多线程程序得时候,只需要搞明白这个到底锁的是什么就不会出错了!



http://www.ppmy.cn/news/76420.html

相关文章

【小菜鸡刷题记】---栈与队列篇

【小菜鸡刷题记】---栈与队列篇 用两个栈模拟一个队列用两个队列实现栈剑指 Offer 58 - I. 翻转单词顺序剑指 Offer 59 - I. 滑动窗口的最大值剑指 Offer 30. 包含min函数的栈剑指 Offer 59 - II. 队列的最大值(max函数的队列) 特此声明:本篇…

【数据库复习整理】数据库为什么要进行分库和分表以及水平分表和垂直分表

分库和分表原因 数据库进行分库和分表是为了解决大规模数据存储和高并发访问的需求,以提高系统的性能、可扩展性和可用性。以下是分库和分表的主要原因和好处: **分库的原因和好处:** 1. **数据隔离:** 分库将数据划分到不同的…

使用 CameraX 在 Jetpack Compose 中构建相机 Android 应用程序

使用 CameraX 在 Jetpack Compose 中构建相机 Android 应用程序 CameraX 是一个 Jetpack 库,旨在帮助简化相机应用程序的开发。 [camerax官方文档] https://developer.android.com/training/camerax CameraX的几个用例: Image CaptureVideo CapturePrev…

自动化早已不是那个自动化了,谈一谈自动化测试现状和自我感受……

前言 从2017年6月开始接触自动化至今,已经有好几年了,从17年接触UI自动化(unittestselenium)到18年接触接口自动化(unittestrequests)再到18年自己编写自动化平台(后台使用python的flask&#…

day10 - 使用canny算子进行人像勾勒

本期主要介绍canny算子,了解canny算子的流程以及各个流程的原理和实现。 ​ 完成本期内容,你可以: 了解canny算子的流程和应用 若要运行案例代码,你需要有: 操作系统:Ubuntu 16 以上 或者 Windows10 工…

嘉兴桐乡考证培训-23年教资认定注意事项你知道吗?

又到了新的一年了,去年错过认定的同学们可以竖起耳朵啦~ 每年认定机会有两次,大部分省份一般上半年下半年各一次。 问:在校生可以认定么? 答:可以,但有年级限制:本科生大四最后一学期&#xf…

Linux多路转接之epoll

文章目录 一、select方案和poll方案还存在的缺陷二、epoll的认识1.epoll的基本认识2.epoll的原理3.epoll函数接口 三、编写epoll服务器四、epoll工作方式1.LT模式2.ET模式 一、select方案和poll方案还存在的缺陷 多路转接方案一开始是select方案,但是select方案缺点…

通过九点选择CRM系统

众所周知,CRM系统对于企业的发展至关重要。它可以帮助企业增强市场竞争力,拓展新的市场机会,提升品牌形象和口碑,提高客户满意度和忠诚度,实现业绩的大幅增长。那么选型时,CRM系统哪家好?看准这…