synchronized工作原理及最佳实践指南

1. synchronized的基本用法与案例分析

1.1. 同步实例方法:对象锁的基本概念

1.1.1. 代码示例:无同步情况下的问题

在没有同步机制的环境下,当多个线程访问同一对象的非同步方法时,会导致资源共享的问题,从而出现数据不一致的现象。

public class Counter {private int count = 0;public void increment() {count++;}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();Thread threadA = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}});Thread threadB = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}});threadA.start();threadB.start();try {threadA.join();threadB.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final count is: " + counter.getCount());}
}

在上述代码中,我们期望最终的count值为20000。但是,由于线程安全问题,实际输出的结果可能会少于20000。

1.1.2. 代码示例:同步实例方法示例

为了解决这个问题,我们可以在increment方法前加上synchronized关键字。

public class Counter {private int count = 0;public synchronized void increment() {count++;}// 其余代码与之前相同
}

加上synchronized后,每次只有一个线程能持有对象锁,从而避免了并发访问时的数据不一致问题。

1.1.3. 案例分析:对象锁的工作机制

当一个线程访问对象的一个synchronized同步方法时,该线程便持有了该方法所在对象的锁。该锁会保护方法中所有的代码,使得其他线程无法同时执行任何其他的synchronized同步方法。

1.2. 同步静态方法:类锁的应用

1.2.1. 代码示例:静态方法同步

和实例方法锁定对象不同,静态方法同步是锁定的类,也就是.class对象。让我们看一个简单的同步静态方法例子:

public class StaticCounter {private static int count = 0;public static synchronized void increment() {count++;}public static int getCount() {return count;}public static void main(String[] args) {Thread threadA = new Thread(StaticCounter::increment);Thread threadB = new Thread(StaticCounter::increment);threadA.start();threadB.start();try {threadA.join();threadB.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final static count is: " + StaticCounter.getCount());}
}

在这个例子中,increment是一个同步静态方法,它会锁定StaticCounter.class类对象,确保多线程操作的线程安全。

1.2.2. 案例分析:类锁与对象锁的区别

类锁和对象锁是两个不同的概念。对象锁是每个实例特有的,不同实例之间的对象锁不会相互影响。而类锁是类级别的,所有实例共享同一个类锁,静态同步方法无论被哪个实例调用,都是同步的。

1.3. 同步代码块:精细控制同步

1.3.1. 代码示例:同步代码块用法

我们可以通过同步代码块来控制同步的粒度,以提高效率:

public class BlockCounter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized(lock) {count++;}}// 其余代码与之前相同
}

在这个例子中,我们没有同步整个方法,仅同步了增加count的那一小部分代码。这样,如果increment方法中还有其他逻辑,它们就不会被同步锁影响。

1.3.2. 案例分析:提升性能的同步策略

使用同步代码块而不是同步整个方法可以显著提高应用程序的性能,因为它减少了线程持有锁的时间。不过,选择正确的锁对象和确保所有相关操作都在同步控制之下仍然至关重要。

2. synchronized的工作原理详解

2.1. 深入JVM:synchronized的内部实现

2.1.1. 从字节码视角理解同步

要理解synchronized的工作原理,首先需要从字节码的角度来看。当我们在方法上使用synchronized关键字时,JVM在编译后的字节码中会使用monitorenter和monitorexit指令来实现同步。

public synchronized void syncMethod() {// 方法体
}编译后的字节码中的关键部分看起来是这样的:0: monitorenter  
// 方法体的字节码
n: monitorexit

当线程进入syncMethod方法时,它将执行monitorenter指令,试图获取对象的监视器锁。如果获取成功,则进入方法体执行;如果获取失败,则该线程进入阻塞状态,直到其他线程释放锁。

2.1.2. 对象头中的Mark Word结构

每个Java对象的对象头中有一部分称为Mark Word,它记录了对象、锁以及垃圾收集相关的信息。当一个线程尝试同步一个对象时,JVM会使用CAS操作(比较并交换)尝试更新这个对象头的Mark Word来获取锁。
如果没有竞争,这个线程将成功地占有锁,并将锁的标记改成指向该线程的锁记录。

2.1.3. 加锁过程的内存语义

synchronized的加锁和解锁过程建立了一个内存屏障,保证了特定操作的顺序性和可见性。简而言之,当线程释放锁时,它之前的操作必须对随后获得同一个锁的其他线程可见。

2.2. 锁的状态变化:偏向锁、轻量级锁与重量级锁

2.2.1. 锁优化:JVM的锁升级过程

为了在不同的竞争情况下提供最佳性能,JVM采用了锁的逐步升级策略,包含偏向锁,轻量级锁及重量级锁。

  • 偏向锁:适用于只有一个线程访问同步块的场景。
  • 轻量级锁:适用于线程交替执行同步块的场景。
  • 重量级锁:适用于多线程同时竞争同步块的场景。

2.2.2. 实战:使用jol(Java Object Layout)工具分析锁状态

我们可以通过jol工具来查看对象在内存中的布局,包括锁的状态。下面是一个简单的使用jol的示例:

import org.openjdk.jol.info.ClassLayout;public class JOLExample {public static void main(String[] args) {Object lock = new Object();System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
}

在这个示例中,我们创建了一个新的Object实例,并打印了它在内存中的布局信息。

2.3. 线程安全机制:监视器模式的实现

2.3.1. Java内存模型(JMM)与线程安全

Java内存模型定义了共享变量的可见性、原子性以及有序性,对实现高效的线程安全机制至关重要。synchronized关键字结合JMM保证了操作的原子性和内存的可见性。

2.3.2. synchronized与wait/notify机制

synchronized另外一个重要的特性是,它可以与Object的wait()和notify()方法结合使用,来实现等待/通知模式。这个模式是多线程协作的一种机制。

public synchronized void waitForCondition() throws InterruptedException {while (someConditionIsNotMet()) {wait();}// 对条件满足后的处理
}public synchronized void notifyConditionChanged() {// 更改条件notifyAll(); // 或 notify()
}

在waitForCondition方法中,如果某个条件不满足,则调用wait(),使当前线程等待。而在notifyConditionChanged方法中,一旦条件变化,它将通过notifyAll()或notify()唤醒所有/一个在等待的线程。

3. 运行结果与多线程行为剖析

3.1. 线程执行示范:展示同步与非同步的差异

3.1.1. 实验环境搭建与测试代码

我们将通过具体的代码示例来展现同步与非同步的运行结果。这需要构建一个可以模拟多线程竞争条件的测试环境。下面是设置这样一个环境的示例代码:

public class SynchronizedExperiment {private int syncCount = 0;private int nonSyncCount = 0;public synchronized void incrementSync() {syncCount++;}public void incrementNonSync() {nonSyncCount++;}public static void main(String[] args) {final SynchronizedExperiment experiment = new SynchronizedExperiment();// 启动一定数量的线程同时进行同步和非同步操作for (int i = 0; i < 1000; i++) {new Thread(() -> experiment.incrementSync()).start();new Thread(() -> experiment.incrementNonSync()).start();}// 等待足够长的时间,确保所有线程操作完成try {Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Synchronized count is: " + experiment.syncCount);System.out.println("Non-synchronized count is: " + experiment.nonSyncCount);}
}

在这个实验中,我们创建了1000个线程执行同步方法incrementSync和1000个线程执行非同步方法incrementNonSync。在没有同步措施的情况下,我们预期nonSyncCount的值小于1000,因为多个线程同时修改同一个变量会导致线程安全问题。

3.1.2. 运行结果解析与问题定位

执行上述程序后,我们通常会发现,同步计数syncCount保持不变,因为synchronized关键字保证了每次只有一个线程能够修改syncCount。然而nonSyncCount可能会小于预期的1000,这是因为多个线程在没有同步的情况下修改同一个变量产生了线程干扰。

3.2. 死锁问题探讨与解决方案

3.2.1. 案例导读:死锁产生的条件

死锁是多线程程序中的一个常见问题,当多个线程在等待对方释放锁,导致所有线程都无法继续执行时就会产生死锁。要发生死锁,以下四个条件必须同时满足:

  • 互斥条件:资源不能被多个线程同时使用。
  • 至少有一个资源被多个线程持有并等待获取更多的资源。
  • 资源不能被线程抢占:线程持有的资源在使用完毕之前不能被其他线程抢占。
  • 循环等待:线程之间形成一种头尾相连的循环等待资源关系。

3.2.2. 避免死锁的设计和开发实践

避免死锁的方法有:

  • 破坏互斥条件:尝试设计算法使多个线程不必互斥地访问资源。
  • 破坏持有和等待条件:可以一次性申请所有资源,避免分步骤获取资源。
  • 破坏不可剥夺条件:如果已经持有资源的线程进一步申请资源时得不到满足,允许它释放已持有的资源。
  • 破坏循环等待条件:给资源编号,只允许以一定顺序申请资源。

通过一系列的代码示例和理论分析,我们演示了synchronized的使用、原理和配合其他语言特性时的行为。在阐述了重要概念并给出了示例之后,读者可以更好地理解如何有效地使用synchronized以及在复杂的多线程环境中如何保证线程安全和性能。

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

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

相关文章

STM32微秒级别延时--F407--TIM1

基本配置&#xff1a; TIM1挂载在APB2总线上&#xff0c;150MHz经过15分频&#xff0c;得到10MHz计数频率&#xff0c;由于disable了自动重装载&#xff0c;所以只需要看下一次计数值是多少即可。 void TIM1_Delay_us(uint16_t us) //使用阻塞方式进行延时&#xff0c;ARR值不…

一次讲透 CSS 背景样式

文章导读&#xff1a;AI 辅助学习前端&#xff0c;包含入门、进阶、高级部分前端系列内容&#xff0c;当前是 CSS 的部分&#xff0c;瑶琴会持续更新&#xff0c;适合零基础的朋友&#xff0c;已有前端工作经验的可以不看&#xff0c;也可以当作基础知识回顾。 CSS 的背景样式…

算法训练营第61天|LeetCode 739. 每日温度 496.下一个更大元素 I

LeetCode 739. 每日温度 题目链接&#xff1a; LeetCode 739. 每日温度 代码&#xff1a; class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<int>st;int size temperatures.size();vector<int>resu…

解决the --read-only option so it cannot execute this statement

程序报错如下&#xff1a; Caused by: org.springframework.jdbc.UncategorizedSQLException: StatementCallback; uncategorized SQLException for SQL [DELETE FROM S_P_USER_Y WHERE Y 2024 ]; SQL state [HY000]; error code [1290]; The MySQL server is running wit…

day23-等差数列划分

问题描述&#xff1a; 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0c;[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你一个整数数组 nums &#xff0c;返回数组 nums 中所有为等差…

【C 数据结构-动态内存管理】4. 无用单元收集(垃圾回收机制)

文章目录 【 1. 问题描述与解决方法 】【 2. 中断回收机制 】 【 1. 问题描述与解决方法 】 问题描述 动态存储管理的运行机制可以概括为&#xff1a;当用户发出申请空间的请求后&#xff0c;系统向用户分配内存&#xff1b;用户运行结束释放存储空间后&#xff0c;系统回收内…

Fizzler库+C#:从微博抓取热点的最简单方法

概述 在这篇技术文章中&#xff0c;我们将深入研究如何利用Fizzler库结合C#语言&#xff0c;以实现从微博平台抓取热点信息的功能。微博作为中国乃至全球范围内具有重要影响力的社交媒体平台之一&#xff0c;在互联网信息传播中扮演着举足轻重的角色。通过Fizzler这一强大的.N…

Pytorch 实现情感分析

情感分析 情感分析是 NLP 一种应用场景&#xff0c;模型判断输入语句是积极的还是消极的&#xff0c;实际应用适用于评论、客服等多场景。情感分析通过 transformer 架构中的 encoder 层再加上情感分类层进行实现。 安装依赖 需要安装 Poytorch NLP 相关依赖 pip install t…

JVM学习笔记【基础篇:垃圾回收】

自动垃圾回收 C/C的内存管理 ⚫ 在C/C这类没有自动垃圾回收机制的语言中&#xff0c;一个对象如果不再使用&#xff0c;需要手动释放&#xff0c;否则就会出现 内存泄漏。我们称这种释放对象的过程为垃圾回收&#xff0c;而需要程序员编写代码进行回收的方式为手动回收。 ⚫ …

RTT潘多拉开发板上实现电源管理

简介 随着物联网(IoT)的兴起&#xff0c;产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作&#xff0c;而作为联网的SOC也需要有快速的响应功能和较低的功耗。 在产品开发的起始阶段&#xff0c;首先考虑是尽快完成产品的功能开发。在产品…

数仓开发中期:理论巩固

一、数仓以及商业智能&#xff08;Data Warehousing and Business Intelligence, DW/BI&#xff09;系统 1.1数据操作和数据获取的区别 对所有组织来说&#xff0c;信息都是其最重要的财富之一。信息几乎总是用作两个目的:操作型记录的保存和分析型决策的制定。简单来说&…

Stack数据结构设计模板

第三章 栈、队列、数组 1.栈 1.1 顺序栈 #define MaxSize 20 typedef int ElemType; //顺序栈的定义 typedef struct {ElemType data[MaxSize];int top; }SqStack; // 初始化顺序栈 void InitSqStack(SqStack &S){S.top -1; }; // 入栈(增) bool Push(SqStack &S,El…

(非常全)前后端分离架构的优势

前后端分离架构在现代Web应用开发中变得越来越流行&#xff0c;它具有许多优势&#xff1a; 职责划分清晰&#xff1a;前后端分离使得前端专注于用户界面和交互&#xff0c;后端专注于业务逻辑和数据处理。这种职责划分有助于提高开发效率&#xff0c;降低维护成本。 开发效率…

WIFI模块UDP电脑端调试

一&#xff0c;两端都是电脑端 1&#xff0c;电脑本机的IP地址 192.168.137.1 2&#xff0c;新建两个不同的连接&#xff0c;注意端口 二&#xff0c;WIFI 模块和电脑端连接 1&#xff0c;设置模块端目标IP和端口&#xff0c;电脑端只接收数据的话&#xff0c;IP、端口可随…

自建的 npm 仓库上发布包

要在自建的 npm 仓库上发布包&#xff0c;你需要按照以下步骤操作&#xff1a; 1. 设置 npm 仓库地址 首先&#xff0c;确保你已经将 npm 配置为使用你的自建仓库。你可以通过以下命令将 npm registry 配置为你的仓库地址&#xff1a; npm config set registry <your-reg…

【从零开始学架构 架构基础】架构设计的本质、历史背景和目的

本文是《从零开始学架构》的第一篇学习笔记&#xff0c;主要理解架构的设计的本质定义、历史背景以及目的。 架构设计的本质 分别从三组概念的区别来理解架构设计。 系统与子系统 什么是系统&#xff0c;系统泛指由一群有关联的个体组成&#xff0c;根据某种规则运作&#…

企业终端安全管理软件有哪些?终端安全管理软件哪个好?

终端安全的重要性大家众所周知&#xff0c;关系到生死存亡的东西。 各类终端安全管理软件应运而生&#xff0c;为企业提供全方位、多层次的终端防护。 有哪些企业终端安全管理软件&#xff1f; 一、主流企业终端安全管理软件 1. 域智盾 域智盾是一款专为企业打造的全面终端…

奥威-金蝶BI现金流量表模板,可借鉴、可套用

企业现金流一旦出了问题都是大问题&#xff0c;会直接影响到企业的日常运作&#xff0c;甚至直接关系到企业能不能继续存活&#xff0c;因此现金流量表是企业财务分析中重要报表之一&#xff0c;也是企业监控财务监控情况的重要手段之一。那么这么重要的一份现金流量表该怎么做…

[Linux] GDB使用指南----包含CentOS7下安装以及使用

什么是GDB&#xff1f; GDB 是由 GUN 软件系统社区提供的调试工具&#xff0c;同 GCC 配套组成了一套完整的开发环境&#xff0c;GDB 是 Linux 和许多 类Unix系统的标准开发环境。可以用来调试C、C、Go、java、 objective-c、PHP等语言。 GDB的作用 程序启动时&#xff0c;可…

73. 矩阵置零/54. 螺旋矩阵

73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 思路&#x…