synchronized 与 volatile 关键字

news2024/9/17 8:26:43

目录

  • 1.前言
  • 1.synchronized 关键字
    • 1. 互斥
    • 2.保证内存可见性
    • 3.可重入
  • 2. volatile 关键字
    • 1.保证内存可见性
    • 2.无法保证原子性
  • 3.synchronized 与 volatile 的区别

1.前言

  synchronized关键字和volatile是大家在Java多线程学习时接触的两个关键字,很多同学可能学习完就忘记了,本文帮助大家回顾以及学习两个关键字的作用,以及说出它们的区别,同时也为了自己学习巩固。

1.synchronized 关键字

1. 互斥

  属于synchronized最关键的特性,可以起到互斥的作用,当某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized 时就会进行阻塞等待

  • 进入synchronized 修饰的代码块此时相当于 加锁
  • 退出synchronized 修饰的代码块此时相当于 释放锁

其解决的问题是在多线程环境下,多个线程对于同一个变量进行读写操作时可能产生的线程安全问题。
如下图代码:

public class Main {
    static int count = 0;
    static void add() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

按照预期,我们希望count的值应该是100000,当执行后输出的答案却是:
在这里插入图片描述
无论多次执行多少次,答案总是与预期相差甚远。这是最简单的多线程安全问题。因为count++这个操作,需要先将count从主内存读入到工作内存,然后增值,再将更改后的值写回主内存,这一系列操作必须保证原子性,而在多线程环境下是无法保证的,所以我们需要加上synchronized进行上锁,以此保证自增这个操作的原子性。
只需更改add方法如下:

 synchronized static void add() {
        count++;
    }

再次运行后答案与预期相符合:
在这里插入图片描述
需要注意一点,synchronized修饰方法时如果是静态方法,则加的是该类对象的锁,如果是成员方法,则加的是对象锁。

2.保证内存可见性

从上面也可以看出synchronized的工作过程:

  • 1.获得互斥锁
  • 2.从主内存拷贝变量的最新副本到工作内存
  • 3.执行代码
  • 4.讲更改后的共享变量的值更新回工作内存
  • 5.释放互斥锁

这样的工作流程,是一定可以保证内存可见性的。当然有的同学并不了解什么是内存可见性,下文讲volatile时我们稍微讲一下,因为它也能保证内存可见性。

3.可重入

synchronized同步块,对于同一条线程来说是可重入的,不会出现将自身锁死的情况。

当然大家可能对 自身锁死 这个情况不太理解,我们举例一个代码:

public class Main {
    //锁对象
    public static Object lock = new Object();

    public static void main(String[] args) {
        //一次加锁
        synchronized (lock) {
            //二次加锁
            synchronized (lock) {
                System.out.println("正确输出");
            }
        }
    }
}

当线程在一次加锁时,会成功加锁,当第二次加锁时,此时lock已被上锁,于是该线程进行阻塞等待,但其实这个锁是被它自己拿着的,它又不进行释放锁操作,于是将自己锁死。这样的锁称之为 不可重入锁

当我们Java中的synchronized是可重入锁,不会出现上面的问题,它可以正确打印:
在这里插入图片描述
如果对上述代码还不够理解,可以再看一个二次加锁的例子:

public class Main {
    public int count = 0;

    synchronized void increase() {
        count++;
    }

    synchronized void increase2() {
        increase();
    }
}

在上诉代码中:

  • increaseincrease2两个方法都加了synchronized ,而且它们的锁对象都是针对当前对象加锁的。
  • 在调用increase2时,会先给该对象上锁,执行调用increase时,会二次上锁(此时上个锁还未释放),这是没问题的,因为synchronized可重入锁

那是否真的上了两把锁呢?
其实并非如此,在可重入锁的内部,包含了 线程持有者计数器 两个信息。

  • 如果某个线程加锁时,发现锁已被占用,但又发现占用的恰好是自己时,那么然后可以获取到这个锁,并让计数器自增
  • 解锁时首先会让计数器自减,但只有真正自减到0时,我们才会真正意义上的将该锁释放,以供其他线程获取到。

2. volatile 关键字

相对于 synchronized来说,大家可能对volatile会比较陌生,我们来看看其有哪些作用。

1.保证内存可见性

在这里插入图片描述
简单来说,线程在工作时,会去主内存中读取数据到工作内存中,然后从工作内存读取数据。但是,线程从工作内存读取数据的速度,要远远的大于从主内存读取数据。

当一个线程大量地从主内存请求同一个变量的值时,它会发现这个值一直没变,此时jvm会 “自作主张” 的进行优化,直接从工作内存读取之前读到的值。这就会导致一个问题,其他线程对这个共享变量值进行修改,这个线程不能及时地被看到,也就读到了一个错误的值。

比如如下代码:

public class Main{
    static int isQuit = 0;

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (isQuit == 0) {

            }
            System.out.println("t线程执行结束");
        });
        t.start();
        Scanner sc = new Scanner(System.in);
        isQuit = sc.nextInt();
        System.out.println("main线程执行结束");
    }
}

执行以后随便输入一个非零整数:
在这里插入图片描述
发现t线程仍然在进行,而main线程已经结束,但按照逻辑其实此时isQuit值被修改为非零,t线程也应该结束。这就是由于内存可见性产生的问题,main线程修改了isQuit的值t线程并不能及时的接收到。

解决的方法也很简单,只需要给isQuit加上volatile关键字,这样每次t线程都会强制去主内存中读取isQuit的值,从而保证了内存可见性。

2.无法保证原子性

  volatile相较于synchronized来说,主要在于其无法保证原子性,也就是对于下面这个程序,即使给count加上volatile,我们也无法让count的值为100000

public class Main {
    static int count = 0;
    static void add() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

3.synchronized 与 volatile 的区别

  根据上面的总结,我们可知:synchronized既可以保证原子性还可以保证内存可见性,而volatile只能保证内存可见性。那有的人是不是肯定想,那我们无脑使用synchronized不就好了吗?
  那肯定不对,synchronized会进行加锁,使得效率大大的较低,而volatile却不会影响效率,所以只有必须要保证原子性的操作,我们才选择synchronized进行加锁。

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

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

相关文章

QSGS四参数随机生长2D软件 quartet structure generation set

软件简介 AbyssFish四参数随机生长2D软件采用四参数随机生长算法quartet structure generation set (QSGS)&#xff0c;可用于构建二维随机孔隙图。 软件提供图片长度、宽度&#xff1b;随机生长算法的分布概率、生长概率、概率密度&#xff08;暂不考虑多相材料相互作用&…

可视化项目管理,控制项目进度,项目经理需要做好以下工作

对于项目的管理者来说&#xff0c;项目信息透明&#xff0c;能够更容易让管理者发现项目中的问题&#xff0c;及时找到问题的原因和相关任务的责任人。 当项目信息能相对精准地呈现给管理者时&#xff0c;也能促进项目成员也能更加认真负责的完成任务&#xff0c;不会找借口推…

Elasticsearch使用——中级篇

在上一篇&#xff0c;已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。本篇&#xff0c;研究下elasticsearch的数据搜索功能。分别使用DSL和RestClient实现搜索。1.DSL查询文档elasticsearch的…

电子技术——功率耗散

电子技术——功率耗散 如今许多集成电路系统都是电池供电的&#xff0c;对于功率耗散限制很严格。其他高性能电路&#xff0c;例如计算机服务器机房产品&#xff0c;有着严格的热耗散功率限制。所以&#xff0c;减小IC中的功率耗散变成了IC设计中最重要的挑战性的设计。 本节…

层次聚类:BIRCH 聚类、Lance–Williams equation

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 BIRCH 聚类 要求数据为向量形式&#xff0c;则通过构建 CF-tree (Clustering Feature Tree) 实现可扩展地高效聚类&#x…

嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板

stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器&#xff0c;集成2个Cortex-A7核和1个Cortex-M4 核&#xff0c;A7核上可以跑Linux操作系统&#xff0c;M4核上可以跑FreeRT…

数据结构之二叉树(上)

文章目录前言一、二叉树的定义二、二叉树的几种情况三、特殊的二叉树1. 满二叉树2. 完全二叉树四、二叉树的存储结构1. 顺序存储2. 链式存储五、二叉树的性质总结前言 本文主要介绍了二叉树的基本概念以及二叉树的存储结构 一、二叉树的定义 一棵二叉树是结点的一个有限集合&…

Fortinet 发布《2022下半年度全球威胁态势研究报告》,七大发现值得关注!

全球网络与安全融合领域领导者Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;近日发布《2022 下半年度全球威胁态势研究报告》。报告指出&#xff0c;相对于组织攻击面的不断扩大以及全球威胁态势的持续演进&#xff0c;网络犯罪分子设计、优化技术与战术…

助你加速开发效率!告别IDEA卡顿困扰的性能优化技巧

在现代软件开发中&#xff0c;IDE&#xff08;集成开发环境&#xff09;是一个必不可少的工具。IntelliJ IDEA是一个广受欢迎的IDE&#xff0c;但有时候IDE的性能可能会受到影响&#xff0c;导致开发人员的工作效率降低。本文将介绍一些可以提高IDE性能的技巧&#xff0c;帮助开…

yii2项目使用frp https2http插件问题

yii2内网项目&#xff0c;使用frp进行内网穿透&#xff0c;使用 https2http插件把内网服务器http流量转成https&#xff0c;会存在一个问题&#xff1a;当使用 $this->redirect(...) 或 $this->goHome() &#xff08;其实用的也是前者&#xff09;等重定向时&#xff0c;…

JavaScript 高级3 :函数进阶

JavaScript 高级3 &#xff1a;函数进阶 Date: January 19, 2023 Text: 函数的定义和调用、this、严格模式、高阶函数、闭包、递归 目标&#xff1a; 能够说出函数的多种定义和调用方式 能够说出和改变函数内部 this 的指向 能够说出严格模式的特点 能够把函数作为参数和返…

Spring Boo集成RocketMQ

一、介绍 Producer&#xff1a;生产者&#xff0c;用来发送消息Consumer&#xff1a;消费者&#xff0c;用来消费消息NameServer&#xff1a;服务注册中心&#xff0c;用于注册生产者、消费者&#xff0c;存储Broker路由 并提供给生产者和消费者Broker&#xff1a;用于存储消息…

【源码】Java版云HIS系统:公立二甲医院应用三年 系统运行稳定、功能齐全

SaaS模式Java版云HIS系统源码&#xff0c;拥有自主知识产权&#xff0c;采用前后端分离架构&#xff0c;前端由Angular语言、JavaScript开发&#xff1b;后端使用Java语言开发。具有强大的可扩展性&#xff0c;二次开发方便快捷。 文末卡片获取联系&#xff01; SaaS模式Java版…

Python 开发-批量 FofaSRC 提取POC 验证

数据来源 学习内容和目的&#xff1a; ---Request 爬虫技术&#xff0c;lxml 数据提取&#xff0c;异常护理&#xff0c;Fofa 等使用说明---掌握利用公开或 0day 漏洞进行批量化的收集及验证脚本开发Python 开发-某漏洞 POC 验证批量脚本---glassfish存在任意文件读取在默认4…

canal admin管理端配置(二)

下载安装 下载地址&#xff1a; 下载解压即可 配置 修改canal.admin-1.1.5\conf\application.yml server:port: 8089 #端口根据是否冲突修改 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8spring.datasource:address: 192.0.16.12:3306#数据库ip和端口…

Day09-网页布局实战定位

文章目录网页布局实战一 表格案例1-单元格的合并案例2-随堂练习二 定位1 文档流2 position 共有四个属性值:3 固定定位案例1-右下角广告案例2-头部固定案例3-div居中4 相对定位案例1-基础案例案例2-文字居于水平线中间5 绝对定位案例1-基础案例6 定位的层次关系 z-index案例1踩…

免费下载丨一看即会,Serverless 技术进阶必读百宝书

过去一年&#xff0c;全球正在加速推进云计算的 Serverless 化进程。Serverless 架构已经逐渐从“被接受”走向了“被学习”和“被应用”。云的产品体系正在 Serverless 化&#xff0c;从计算、存储、数据库到中间件&#xff0c;越来越多的云产品采用了 Serverless 模式。服务器…

筑基九层 —— 指针详解

目录 前言&#xff1a; 指针详解 前言&#xff1a; 1.CSDN由于我的排版不怎么好看&#xff0c;我的有道云笔记比较美观&#xff0c;请移步有道云笔记 2.修炼必备 1&#xff09;入门必备&#xff1a;VS2019社区版&#xff0c;下载地址&#xff1a;Visual Studio 较旧的下载 -…

现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类

专栏&#xff1a;神经网络复现目录 本章介绍的是现代神经网络的结构和复现&#xff0c;包括深度卷积神经网络&#xff08;AlexNet&#xff09;&#xff0c;VGG&#xff0c;NiN&#xff0c;GoogleNet&#xff0c;残差网络&#xff08;ResNet&#xff09;&#xff0c;稠密连接网络…

【Hello Linux】进程控制 (内含思维导图)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念 进程控制介绍进程创建fork函数fork函数的返回值fork函数的使用…