Synchronized 的几种用法解析

背景介绍

说到并发编程,总绕不开线程安全的问题。

实际上,在多线程环境中,难免会出现多个线程对一个对象的实例变量进行同时访问和操作,如果编程处理不当,会产生脏读现象。

线程安全问题

我们先来看一个简单的线程安全问题的例子!

public class DataEntity {private int count = 0;public void addCount(){count++;}public int getCount(){return count;}
}public class MyThread extends Thread {private DataEntity entity;public MyThread(DataEntity entity) {this.entity = entity;}@Overridepublic void run() {for (int j = 0; j < 1000000; j++) {entity.addCount();}}
}public class MyThreadTest {public static void main(String[] args) {// 初始化数据实体DataEntity entity = new DataEntity();//使用多线程编程对数据进行计算for (int i = 0; i < 10; i++) {MyThread thread = new MyThread(entity);thread.start();}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + entity.getCount());}
}

多次运行结果如下:

第一次运行:result: 9788554
第二次运行:result: 9861461
第三次运行:result: 6412249
...

上面的代码中,总共开启了 10 个线程,每个线程都累加了 1000000 次,如果结果正确的话,自然而然总数就应该是 10 * 1000000 = 10000000。

但是多次运行结果都不是这个数,而且每次运行结果都不一样,为什么会出现这个结果呢?

简单的说,这是主内存和线程的工作内存数据不一致,以及多线程执行时无序,共同造成的结果!

JMM内存模型

在这里插入图片描述

如上图所示,线程 1 和线程 2 之间,如果要完成数据通信的话,需要经历以下几个步骤:

  • 线程 1从主内存中将共享变量读入线程 1的工作内存后并进行操作,之后将数据重新写回到主内存中;
  • 线程 2从主存中读取最新的共享变量,然后存入自己的工作内存中,再进行操作,数据操作完之后再重新写入到主内存中;

如果线程 1更新后数据并没有及时写回到主存,而此时线程 2 从主内存中读到的数据,可能就是过期的数据,于是就会出现“脏读”现象。

针对多线程编程中,程序运行不安全的问题,Java 提供了synchronized关键字来解决这个问题,当多个线程同时访问共享资源时,会保证线程依次排队操作共享变量,从而保证程序的实际运行结果与预期一致。

我们对上面示例中的DataEntity.addCount()方法进行改造,再看看效果如下。

   public class DataEntity {private int count = 0;/*** 在方法上加上 synchronized 关键字*/public synchronized void addCount(){count++;}public int getCount(){return count;}}

多次运行结果如下:

第一次运行:result: 10000000
第二次运行:result: 10000000
第三次运行:result: 10000000
...

运行结果与预期一致!

synchronized 使用详解

synchronized作为 Java 中的关键字,在多线程编程中,有着非常重要的地位,从功能角度看,它有以下几个比较重要的特性:

  • 原子性:即一个或多个操作要么全部执行成功,要么全部执行失败。synchronized关键字可以保证只有一个线程拿到锁,访问共享资源
  • 可见性:即一个线程对共享变量进行修改后,其他线程可以立刻看到。执行synchronized时,线程获取锁之后,一定从主内存中读取数据,释放锁之前,一定会将数据写回主内存,从而保证内存数据可见性
  • 有序性:即保证程序的执行顺序会按照代码的先后顺序执行。synchronized关键字,可以保证每个线程依次排队操作共享变量

synchronized也被称为同步锁,它可以把任意一个非 NULL 的对象当成锁,只有拿到锁的线程能进入方法体,并且只有一个线程能进入,其他的线程必须等待锁释放了才能进入,它属于独占式的悲观锁,同时也属于可重入锁。

从实际的使用角度来看,synchronized修饰的对象有以下几种:

  • 修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
  • 修饰一个静态的方法:其作用的范围是整个静态方法,作用的对象是这个类的所有对象
  • 修饰一个代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象,使用上比较灵活

1、修饰一个方法

当synchronized修饰一个方法时,多个线程访问同一个对象,哪个线程持有该方法所属对象的锁,就拥有执行权限,否则就只能等待。

如果多线程访问的不是同一个对象,不会起到保证线程同步的作用。

示例如下:

public class DataEntity {private int count;/*** 在方法上加上 synchronized 关键字*/public synchronized void addCount(){for (int i = 0; i < 3; i++) {try {System.out.println(Thread.currentThread().getName() + ":" + (count++));Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public int getCount() {return count;}
}public class MyThreadA extends Thread {private DataEntity entity;public MyThreadA(DataEntity entity) {this.entity = entity;}@Overridepublic void run() {entity.addCount();}
}public class MyThreadB extends Thread {private DataEntity entity;public MyThreadB(DataEntity entity) {this.entity = entity;}@Overridepublic void run() {entity.addCount();}
}public class MyThreadTest {public static void main(String[] args) {// 初始化数据实体DataEntity entity = new DataEntity();MyThreadA threadA = new MyThreadA(entity);threadA.start();MyThreadB threadB = new MyThreadB(entity);threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + entity.getCount());}
}

运行结果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

当两个线程共同操作一个对象时,此时每个线程都会依次排队执行。

假如两个线程操作的不是一个对象,此时没有任何效果,示例如下:

   public class MyThreadTest {public static void main(String[] args) {DataEntity entity1 = new DataEntity();MyThreadA threadA = new MyThreadA(entity1);threadA.start();DataEntity entity2 = new DataEntity();MyThreadA threadB = new MyThreadA(entity2);threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + entity1.getCount());System.out.println("result: " + entity2.getCount());}}

运行结果如下:

Thread-0:0
Thread-1:0
Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
result: 3
result: 3

从结果上可以看出,当synchronized修饰一个方法,当多个线程访问同一个对象的方法,每个线程会依次排队;如果访问的不是一个对象,线程不会进行排队,像正常执行一样。

2、修饰一个静态的方法

synchronized修改一个静态的方法时,代表的是对当前.java文件对应的 Class 类加锁,不区分对象实例。

示例如下:

public class DataEntity {private static int count;/*** 在静态方法上加上 synchronized 关键字*/public synchronized static void addCount(){for (int i = 0; i < 3; i++) {try {System.out.println(Thread.currentThread().getName() + ":" + (count++));Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public static int getCount() {return count;}
}public class MyThreadA extends Thread {@Overridepublic void run() {DataEntity.addCount();}
}public class MyThreadB extends Thread {@Overridepublic void run() {DataEntity.addCount();}
}public class MyThreadTest {public static void main(String[] args) {MyThreadA threadA = new MyThreadA();threadA.start();MyThreadB threadB = new MyThreadB();threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + DataEntity.getCount());}
}

运行结果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

静态同步方法和非静态同步方法持有的是不同的锁,前者是类锁,后者是对象锁,类锁可以理解为这个类的所有对象。

3、修饰一个代码块

synchronized用于修饰一个代码块时,只会控制代码块内的执行顺序,其他试图访问该对象的线程将被阻塞,编程比较灵活,在实际开发中用的应用比较广泛。

示例如下

 public class DataEntity {private int count;/*** 在方法上加上 synchronized 关键字*/public void addCount(){synchronized (this){for (int i = 0; i < 3; i++) {try {System.out.println(Thread.currentThread().getName() + ":" + (count++));Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}public int getCount() {return count;}}public class MyThreadTest {public static void main(String[] args) {// 初始化数据实体DataEntity entity = new DataEntity();MyThreadA threadA = new MyThreadA(entity);threadA.start();MyThreadB threadB = new MyThreadB(entity);threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + entity.getCount());}}

运行结果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

其中synchronized (this)中的this,表示的是当前类实例的对象,效果等同于public synchronized void addCount()。

除此之外,synchronized()还可以修饰任意实例对象,作用的范围就是具体的实例对象。

比如,修饰个自定义的类实例对象,作用的范围是拥有lock对象,其实也等价于synchronized (this)。

public class DataEntity {private Object lock = new Object();/*** synchronized 可以修饰任意实例对象*/public void addCount(){synchronized (lock){// todo...}}
}

当然也可以用于修饰类,表示类锁,效果等同于public synchronized static void addCount()。

public class DataEntity {/*** synchronized 可以修饰类,表示类锁*/public void addCount(){synchronized (DataEntity.class){// todo...}}
}

synchronized修饰代码块,比较经典的应用案例,就是单例设计模式中的双重校验锁实现。

public class Singleton {  private volatile static Singleton singleton;  private Singleton (){}  public static Singleton getSingleton() {  if (singleton == null) {  synchronized (Singleton.class) {  if (singleton == null) {  singleton = new Singleton();  }  }  }  return singleton;  }  
}

采用代码块的实现方式,编程会更加灵活,可以显著的提升并发查询的效率。

synchronized 锁重入介绍

synchronized关键字拥有锁重入的功能,所谓锁重入的意思就是:当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁,而无需等待。

 public class DataEntity {private int count = 0;public synchronized void addCount1(){System.out.println(Thread.currentThread().getName() + ":" + (count++));addCount2();}public synchronized void addCount2(){System.out.println(Thread.currentThread().getName() + ":" + (count++));addCount3();}public synchronized void addCount3(){System.out.println(Thread.currentThread().getName() + ":" + (count++));}public int getCount() {return count;}}public class MyThreadA extends Thread {private DataEntity entity;public MyThreadA(DataEntity entity) {this.entity = entity;}@Overridepublic void run() {entity.addCount1();}}public class MyThreadB extends Thread {private DataEntity entity;public MyThreadB(DataEntity entity) {this.entity = entity;}@Overridepublic void run() {entity.addCount1();}}public class MyThreadTest {public static void main(String[] args) {// 初始化数据实体DataEntity entity = new DataEntity();MyThreadA threadA = new MyThreadA(entity);threadA.start();MyThreadB threadB = new MyThreadB(entity);threadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("result: " + entity.getCount());}}

运行结果如下:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-1:3
Thread-1:4
Thread-1:5
result: 6

从结果上看线程没有交替执行,线程Thread-0获取到锁之后,再次调用其它带有synchronized关键字的方法时,可以快速进入,而Thread-1线程需等待对象锁完全释放之后再获取,这就是锁重入。

小结

从上文中我们可以得知,在多线程环境下,恰当的使用synchronized关键字可以保证线程同步,使程序的运行结果与预期一致。

  • 当synchronized修饰一个方法时,作用的范围是整个方法,作用的对象是调用这个方法的对象;
  • 当synchronized修饰一个静态方法时,作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  • 当synchronized修饰一个代码块时,作用的范围是代码块,作用的对象是修饰的内容,如果是类,则这个类的所有对象都会受到控制;如果是任意对象实例子,则控制的是具体的对象实例,谁拥有这个对象锁,就能进入方法体;

synchronized是一种同步锁,属于独占式,使用它进行线程同步,JVM 性能开销很大,大量的使用未必会带来好处。

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

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

相关文章

rhce作业4

问题&#xff1a; 1.搭建dns服务器能够对自定义的正向或者反向域完成数据解析查询。 2.配置从DNS服务器&#xff0c;对主dns服务器进行数据备份。 配置&#xff1a; 主服务器配置 安装 关闭防火墙 主配置文件定义正反向解析域 正向解析资源记录文件 反向解析记录文件 重启…

在数据抓取的时候,短效IP比长效IP有哪些优势?

在数据抓取领域&#xff0c;代理IP的选择对于任务的成功率和效率至关重要。短效IP和长效IP各有其特点和适用场景&#xff0c;但在数据抓取过程中&#xff0c;短效IP因其独特的优势而受到青睐。本文将和大家一起探讨短效IP在数据抓取中相比长效IP的优势。 短效IP的定义与特点 …

B2C分销管理系统(源码+文档+部署+讲解)

本文将深入解析“B2C分销管理系统”的项目&#xff0c;探究其架构、功能以及技术栈&#xff0c;并分享获取完整源码的途径。 系统概述 "B2C分销管理系统"是一款集产品管理、客户服务、运营监控和财务管理于一体的综合性分销管理平台。系统功能全面覆盖套餐管理、SI…

【前端基础】CSS基础

目标&#xff1a;掌握 CSS 属性基本写法&#xff0c;能够使用文字相关属性美化文章页。 01-CSS初体验 层叠样式表 (Cascading Style Sheets&#xff0c;缩写为 CSS&#xff09;&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现&#xff08;美化内容&#…

CertiK发现三星区块链密钥库的高风险漏洞,第3次获得致谢

2024年11月5日&#xff0c;CertiK因发现三星手机区块链密钥库&#xff08;Blockchain Keystore&#xff09;的一个高风险漏洞&#xff0c;第3次获得三星的认可与致谢。三星Keystore通过移动硬件安全技术&#xff0c;为私钥的存储与签名过程提供坚固的防护。该漏洞得到及时修复&…

阿里云docker安装禅道记录

docker network ls docker network create -d bridge cl_network sudo docker run --name zentao --restart always -p 9982:80 --networkcl_network -v /data/zentao:/data -e MYSQL_INTERNALtrue -d hub.zentao.net/app/zentao:18.5 升级禅道 推荐用按照此文档升级&a…

DFA算法实现敏感词过滤

DFA算法实现敏感词过滤 需求&#xff1a;检测一段文本中是否含有敏感词。 比如检测一段文本中是否含有&#xff1a;“滚蛋”&#xff0c;“滚蛋吧你”&#xff0c;“有病”&#xff0c; 可使用的方法有&#xff1a; 遍历敏感词&#xff0c;判断文本中是否含有这个敏感词。 …

远程控制项目第四天 功能实现

发送屏幕内容 代码详解 1. 创建 CImage 对象并获取屏幕内容 首先&#xff0c;我们创建一个 CImage 对象&#xff0c;用于接收屏幕上的内容。要获取屏幕内容&#xff0c;我们需要先获取当前设备上下文&#xff08;DC&#xff09;。调用 ::GetDC(NULL) 函数&#xff0c;参数 NU…

多分类logistic回归分析案例教程

因变量为无序多分类变量&#xff0c;比如研究成人早餐选择的相关因素&#xff0c;早餐种类包括谷物类、燕麦类、复合类&#xff0c;此时因变量有三种结局&#xff0c;而且三种早餐是平等的没有顺序或等级属性&#xff0c;此类回归问题&#xff0c;可以使用多分类Logistic回归进…

工业相机常用功能之白平衡及C++代码分享

目录 1、白平衡的概念解析 2、相机白平衡参数及操作 2.1 相机白平衡参数 2.2 自动白平衡操作 2.3 手动白平衡操作流程 3、C++ 代码从XML读取参数及设置相机参数 3.1 读取XML 3.2 C++代码,从XML读取参数 3.3 给相机设置参数 1、白平衡的概念解析 白平衡(White Balance)…

越权访问漏洞

V2Board Admin.php 越权访问漏洞 ## 漏洞描述 V2board面板 Admin.php 存在越权访问漏洞&#xff0c;由于部分鉴权代码于v1.6.1版本进行了修改&#xff0c;鉴权方式变为从Redis中获取缓存判定是否存在可以调用… V2Board Admin.php 越权访问漏洞 漏洞描述 V2board面板 Admin.ph…

[C++ 核心编程]笔记 4.4.2 类做友元

4.4.2 类做友元 关键步骤: 在另一个类中使用 friend class 类名 拓展: 在类外写成员函数(已在类内声明, 实现可以写在外面): //类外写成员函数 Building::Building() {m_SittingRoom "客厅";m_BedRoom "卧室"; }GoodGay::GoodGay() {//创建建筑物对象…

W外链如何设置活码功能?

根据搜索结果&#xff0c;W外链平台的活码功能允许用户创建动态二维码&#xff0c;这些二维码背后可以链接到不同的目标链接或页面&#xff0c;并且可以根据预设条件自动更新跳转链接。以下是W外链平台活码功能的一些关键特点和使用步骤 &#xff1a; 1.活码的特点&#xff1a…

【论文复现】基于图卷积网络的轻量化推荐模型

本文所涉及所有资源均在这里可获取。 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐、摄影的一位博主。 &#x1f4d7;本文收录于论文复现系列&#xff0c;大家有兴趣的可以看一看…

QT 实现绘制汽车仪表盘

1.界面实现效果 以下是具体的项目需要用到的效果展示,通常需要使用QPainter类来绘制各种图形和文本,包括一个圆形的仪表盘、刻度、指针和数字。 2.简介 分为以下几个部分,首先设置抗锯齿 painter.setRenderHint(QPainter::Antialiasing)。 QPainter p(this);p.setRender…

2020年美国总统大选数据分析与模型预测

数据集取自&#xff1a;2020年&#x1f1fa;&#x1f1f8;&#x1f1fa;&#x1f1f8;美国大选数据集 - Heywhale.com 前言 对2020年美国总统大选数据的深入分析&#xff0c;提供各州和县层面的投票情况及选民行为的可视化展示。数据预处理阶段将涉及对异常值的处理&#xff0…

sqlserver使用bak文件恢复数据库

进入数据库 sqlcmd -S localhost -U SA -P password备份文件 #备份格式BACKUP DATABASE your_database_name TO DISK path_to_backup_file.bak;#举例 1> BACKUP DATABASE XJZDataTest TO DISK /root/mssql.bak; 2> go使用备份文件恢复数据库 1、查询备份文件中的数据…

拥有一个智能化清理电脑垃圾的工具 是一个非常明智的选择 效率高安全删除文件

拥有一个智能化清理电脑垃圾的工具 是一个非常明智的选择 效率高&安全删除文件。大家以前清理电脑垃圾是不是都是只删除回收站垃圾&#xff0c;或者有些人更聪明一点就会删除临时文件&#xff0c;仅仅清理这些垃圾是不够的&#xff0c;C盘其实还有更多的垃圾需要清理。 太…

RK3568 Android12跳过认证 预置谷歌服务GMS

在Rom开发中需要发布海外版本时基本都需要内置google服务,而规范方式集成的话都需要设备进行认证,获取google应用签名等非常复杂的一套流程,一般大厂才有这些资质和资源,这里介绍一种非常规方式集成GMS,跳过设置认证流程,在RK3568 android12环境亲测有效。 谷歌全家桶中…

[蓝桥杯算法从小白到大牛]动态规划第二讲:三步问题

目录 1->题目链接 2->题目解析 3->讲解算法原理 核心流程: 3.1->状态表示 3.2->状态转移方程(最重要的一步) 3.3->初始化 3.4->填表顺序 3.5->返回值 4->编写代码实现 5->您的专属鼓励师 1->题目链接 三步问题 2->题目解析 题目…