「 代码性能优化 」作为一名Java程序员,你真的了解 synchronized 吗?(三)

简介: 文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。更多、更体系化的内容请持续关注博主,您的 关注、点赞、收藏 都将是小编持续创作的动力!

前言

文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。

更多、更体系化的内容请持续关注博主,您的 关注、点赞、收藏 都将是小编持续创作的动力!

Synchronized关键字用法

前文中提到synchronized 的用法可以从两个维度上面分类,下面蒋总结讨论具体用法:

1. 根据修饰对象分类

1.1 修饰代码块

synchronized(this|object) {}

synchronized(类.class) {}

1.2 修饰方法

修饰非静态方法

修饰静态方法

2. 根据获取的锁分类

2.1 获取对象锁

synchronized(this|object) {}

修饰非静态方法

2.2 获取类锁

synchronized(类.class) {}

修饰静态方法

Synchronized修饰实例方法

示例:

publicclassSyncDemo {
privateintcount;
publicsynchronizedvoidadd() {
count++;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
SyncDemosyncDemo=newSyncDemo();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
syncDemo.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
syncDemo.add();
      }
    });
t1.start();
t2.start();
t1.join(); // 阻塞住线程等待线程 t1 执行完成t2.join(); // 阻塞住线程等待线程 t2 执行完成System.out.println(syncDemo.count);// 输出结果为 20000  }
}

 

在上面的代码当中的add方法只有一个简单的count++操作,因为这个方法是使用synchronized修饰的因此每一个时刻只能有一个线程执行add方法,因此上面打印的结果是20000。如果add方法没有使用synchronized修饰的话,那么线程t1和线程t2就可以同时执行add方法,这可能会导致最终count的结果小于20000,因为count++操作不具备原子性。

Synchronized修饰静态方法

示例:

publicclassSyncDemo {
privatestaticintcount;
publicstaticsynchronizedvoidadd() {
count++; 
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
SyncDemo.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
SyncDemo.add();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(SyncDemo.count); // 输出结果为 20000  }
}

上面的代码最终输出的结果也是20000,但是与前一个程序不同的是。这里的add方法用static修饰的,在这种情况下真正的只能有一个线程进入到add代码块,因为用static修饰的话是所有对象公共的,因此和前面的那种情况不同,不存在两个不同的线程同一时刻执行add方法。

Sychronized修饰多个方法

示例:

publicclassAddMinus {
publicstaticintans;
publicstaticsynchronizedvoidadd() {
ans++;
  }
publicstaticsynchronizedvoidminus() {
ans--;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
AddMinus.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
AddMinus.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(AddMinus.ans); // 输出结果为 0  }
}

在上面的代码当中我们用synchronized修饰了两个方法,addminus。这意味着在同一个时刻这两个函数只能够有一个被一个线程执行,也正是因为addminus函数在同一个时刻只能有一个函数被一个线程执行,这才会导致ans最终输出的结果等于0。

对于一个实例对象来说:

publicclassAddMinus {
publicintans;
publicsynchronizedvoidadd() {
ans++;
  }
publicsynchronizedvoidminus() {
ans--;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
AddMinusaddMinus=newAddMinus();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
addMinus.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
addMinus.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(addMinus.ans);
  }
}

上面的代码没有使用static关键字,因此我们需要new出一个实例对象才能够调用addminus方法,但是同样对于AddMinus的实例对象来说同一个时刻只能有一个线程在执行add或者minus方法,因此上面代码的输出同样是0。

Synchronized修饰实例方法代码块

示例:

publicclassCodeBlock {
privateintcount;
publicvoidadd() {
System.out.println("进入了 add 方法");
synchronized (this) {
count++;
    }
  }
publicvoidminus() {
System.out.println("进入了 minus 方法");
synchronized (this) {
count--;
    }
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
CodeBlockcodeBlock=newCodeBlock();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
codeBlock.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
codeBlock.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(codeBlock.count); // 输出结果为 0  }
}

上面的代码当中addminus方法没有使用synchronized进行修饰,因此一个时刻可以有多个线程执行这个两个方法。在上面的synchronized代码块当中我们使用了this对象作为锁对象,只有拿到这个锁对象的线程才能够进入代码块执行,而在同一个时刻只能有一个线程能够获得锁对象。也就是说add函数和minus函数用synchronized修饰的两个代码块同一个时刻只能有一个代码块的代码能够被一个线程执行,因此上面的结果同样是0。

Synchronized修饰静态代码块

示例:

publicclassCodeBlock {
privatestaticintcount;
publicstaticvoidadd() {
System.out.println("进入了 add 方法");
synchronized (CodeBlock.class) {
count++;
    }
  }
publicstaticvoidminus() {
System.out.println("进入了 minus 方法");
synchronized (CodeBlock.class) {
count--;
    }
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
CodeBlock.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
CodeBlock.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(CodeBlock.count);
  }
}

上面代码的锁对象是CodeBlock.class,这个时候他不再是锁住一个对象了,而是一个类了,这个时候的并发度就变小了,当锁对象是CodeBlock.class的时候,实例对象之间时不能够并发的,因为这个时候的锁对象是一个类。

总结

Synchronized修饰实例方法:不同的对象之间是可以并发的;
Synchronized修饰静态实例方法:不同的对象是不能并发的,但是不同的类之间可以进行并发;
Sychronized修饰多个方法:多个方法在同一时刻只能有一个方法被执行,而且只能有一个线程能够执行;
Synchronized修饰实例方法代码块:同一个时刻只能有一个线程执行代码块;
Synchronized修饰静态代码块:同一个时刻只能有一个线程执行这个代码块,而且不同的对象之间不能够进行并发;

参考&致谢

深入Synchronized各种使用方法

莫笑少年江湖梦,谁不少年梦江湖.感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!

目录
打赏
0
0
1
0
124
分享
相关文章
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
368 33
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-2)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
62 0
|
2月前
|
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
89 0
Java 面试资料中相关代码使用方法与组件封装方法解析
这是一份详尽的Java面试资料代码指南,涵盖使用方法与组件封装技巧。内容包括环境准备(JDK 8+、Maven/Gradle)、核心类示例(问题管理、学习进度跟踪)、Web应用部署(Spring Boot、前端框架)、单元测试及API封装。通过问题库管理、数据访问组件、学习进度服务和REST接口等模块化设计,帮助开发者高效组织与复用功能,同时支持扩展如用户认证、AI推荐等功能。适用于Java核心技术学习与面试备考,提升编程与设计能力。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
66 6
Java 面试资料中相关代码使用方法与组件封装方法解析
基于Java 17 + Spring Boot 3.2 + Flink 1.18的智慧实验室管理系统核心代码
这是一套基于Java 17、Spring Boot 3.2和Flink 1.18开发的智慧实验室管理系统核心代码。系统涵盖多协议设备接入(支持OPC UA、MQTT等12种工业协议)、实时异常检测(Flink流处理引擎实现设备状态监控)、强化学习调度(Q-Learning算法优化资源分配)、三维可视化(JavaFX与WebGL渲染实验室空间)、微服务架构(Spring Cloud构建分布式体系)及数据湖建设(Spark构建实验室数据仓库)。实际应用中,该系统显著提升了设备调度效率(响应时间从46分钟降至9秒)、设备利用率(从41%提升至89%),并大幅减少实验准备时间和维护成本。
82 0
【高薪程序员必看】万字长文拆解Java并发编程!(1)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
56 0
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-1)
🔥【高薪程序员必看】万字长文拆解Java并发编程!面试官看了直呼内行,90%人不知道的线程安全骚操作!💻🚀《16个高频面试灵魂拷问+底层源码暴击》🔥👉戳这里看如何用1个月经验吊打3年程序员!📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!
45 0
|
2月前
|
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
46 0
|
2月前
|
【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析
wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。
51 0
|
2月前
|
【高薪程序员必看】万字长文拆解Java并发编程!(4-1):悲观锁底层原理与性能优化实战
目录4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
46 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等