synchronized关键字

news2025/7/12 11:30:25

多线程编程中,最让人头疼的问题莫过于线程安全,如果对存在线程安全问题的代码不加以处理,可能会带来严重的后果,例如用两个线程对同一个变量进行增加操作

class Counter {
    //这个 变量 是两个线程要去自增的变量
    public int count;
    public void  increase() {
        count++;
    }
}

public class Demo15 {
    private static Counter counter = new Counter();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for(int i = 0; i < 50000; ++i) {
                counter.increase();
            }
        });
        t1.start();

        Thread t2 = new Thread(()->{
            for(int i = 0; i < 50000; ++i) {
                counter.increase();
            }
        });
        t2.start();

        //等待t1和t2执行完,再打印count的结果
        t1.join();
        t2.join();

        //在main中打印一下两个线程自增完成后,得到的count结果
        System.out.println(counter.count);

        //如果不加锁,极端情况
        //所有的操作都是串行的,最终结果就是10w(可能出现,极小概率事件)
        //所有操作都是交错的(并行),最终结果就是5w(可能出现,极小概率事件)
    }
}

在这里插入图片描述

预期结果为10w,但实际结果却为67627,这就是线程安全问题导致的。

产生线程不安全的原因有很多:

  1. 线程是抢占式执行的,线程之间的调度充满随机性(线程不安全的万恶之源,但是我们无可奈何)
  2. 多个线程对同一个变量进行修改操作(如果多个线程针对不同的变量进行修改,没事。如果多个线程针对同一个变量读,也没事),上诉代码线程不安全就是这个原因导致的
  3. 针对变量的操作不是原子的(针对有些操作,比如读取变量的值,只是对应的一条机器指令,此时这样的操作本身就可以视为原子的。通过加锁操作,也可以把好几条指令打包成原子的)
  4. 内存可见性也会影响到线程安全。例如针对同一个变量,线程A进行循环读取,但循环内部并不修改,此时编译器就会优化,把这个变量从内存保存到寄存器中,每次都读取寄存器中的内容(这样做是为了提高效率,因为寄存器的读写效率高于内存),此时线程B修改了这个变量(从内存中读到CPU上,修改完毕后,再写回内存),但不会影响线程A,因为线程A并没有从内存中读取这个变量。在Java中,内存可见性用关键字volatile保证,但它不保证原子性
  5. 指令重排序也会影响到线程安全,不了解的可以看我之前写的博客指令重排问题。大部分代码,彼此的顺序,谁在前,谁在后,无所谓,些代码却依赖前后关系,编译器就会智能的调整代码的前后顺序,从而提高程序的效率,但是应该保证逻辑不变的情况下,再去调整顺序。如果代码是单线程的程序,编译器的判定一般都是很准的,但是如果代码是多线程,编译器也可能存在误判

上述介绍的这5种情况,都是产生线程不安全的原因

对此我们应该对increase()方法加锁或者count这个变量加锁,在C++中需要创建mutex变量,再加锁,然后再解锁,个人觉得有点麻烦(C++加锁的方式有很多)。在Java中,加锁的方式也有很多,最简单,最常用的方式就是在increase()方法最前面加上synchronized关键字,将整个方法锁住,这样就解决了线程安全问题

class Counter {
    //这个 变量 是两个线程要去自增的变量
    public int count;
    synchronized public void  increase() {
        count++;
    }
}

在这里插入图片描述

synchronized 会自动加锁,本质是修改了Object对象中的"对象头"里面的一个标记
synchronized是个可重入锁,可重入锁内部会记录当前的锁被哪个线程占用,同时也会记录一个"加锁次数(引用计数)"。当锁的计数减到0后,就解锁,可重入锁的意义就是降低了使用成本,提高了开发效率,但是也带来了更大的开销(维护锁属于哪个线程,并增加了计数,降低了运行效率)

synchronized 的使用方法
1.直接修饰普通的方法
使用synchronized的时候,本质就是针对某个"对象"进行加锁,此时锁对象就是this

在这里插入图片描述

2.直接修饰代码块
需要显示指定哪个对象需要加锁(Java中的任何对象都可以作为锁对象)

在这里插入图片描述

3.修饰静态方法(更严谨的叫法应该是"类方法")
相当于针对当前类的类对象加锁

在这里插入图片描述

synchronized

  1. 既是一个乐观锁,也是一个悲观锁(根据锁竞争的激烈程度,自适应)
  2. 是一个普通的互斥锁
  3. 既是一个轻量级锁,也是一个重量级锁(根据锁竞争的激烈程度,自适应)
  4. 轻量级锁的部分基于自旋锁实现,重量级的部分基于挂起等待锁实现
  5. 非公平锁
  6. 可重入锁

synchronized几个典型的优化手段(只考虑JDK1.8)

1.锁膨胀/所升级

体现synchronized能够"自适应"这样的能力
代码还能执行到synchronized部分,此时处于无锁状态
当首个线程执行到了synchronized部分,此时就会进入偏向锁状态,偏向锁只是做了一个标记,并没有真的加锁,这样带来的好处就是后续如果没有线程竞争,就避免了加锁,解锁带来的开销
如果此时又有其他线程执行到了synchronized部分,产生锁竞争,此时进入**轻量级锁(自旋锁)状态
如果竞争进一步加剧,就会进入
重量级锁(互斥锁)**状态

无锁——>偏向锁——>轻量级锁(自旋锁)——>重量级锁(互斥锁)

2.锁粗化/细化

此处的粗细是指"锁的粒度"
"锁的粒度"是指加锁的代码涉及到的范围
加锁的代码范围越大,认为锁的粒度越粗
加锁的代码范围越小,认为锁的粒度越细

到底锁的粒度是粗好,还是细好?各有各的好
如果锁的粒度比较细,多个线程之间的并发性就更高
如果锁的粒度比较粗,加锁解锁的开销就更小

Java编译器就会有一个优化,会自动判定(一般来说编译器优化后,效率会变高,但也有意外情况)
如果两次加锁之间的间隔较大(中间隔的代码多),会细化(一般不会进行这种优化)
如果两次加锁之间的间隔较小(中间隔的代码少),会粗化(很可能触发这个优化)


锁消除

有些代码,明明不用加锁,结果你给上锁了,编译器就会发现这个加锁操作好像没什么必要,就直接把锁给去掉了

例如给单线程进行加锁,这个时候编译器就会进行锁消除,
单线程中使用到了StringBuffer,Vector等,它们都是在标准库中进行的加锁操作,实际使用的时候可能存在锁消除

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

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

相关文章

进程调度算法详解

进程调度算法&#x1f3de;️1. 调度指标&#x1f301;2. 先进先出&#xff08;FIFO&#xff09;&#x1f320;3. 最短作业优先&#xff08;SJF&#xff09;&#x1f30c;4. 最短剩余时间优先&#xff08;STCF&#xff09;&#x1f33f;5. 新度量指标&#xff1a;响应时间&…

linux网络编程(四)多路I/O转接服务器

文章目录1.多路I/O转接服务器2.select 方式的多路I/O转接服务器3.poll 方式的多路I/O转接服务器4.epoll 方式的多路I/O转接服务器1.多路I/O转接服务器 多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是&#xff0c;不再由应用程序自己监视客户端连接&#xf…

利用stream实现行政区域列表转tree树形结构

一、数据结构 CREATE TABLE t_districts (adcode bigint NOT NULL COMMENT 主键(区域编码)\r\n,pid varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 父级区域编码,name varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci D…

MySQL的Redo log 、Undo log、 Binlog

MySQL的redo log 、undo log、 binlog redo log概念 redo log翻译过来叫重做日志&#xff0c;是一种保证持久化的措施&#xff0c;innodb存储引擎的物理日志文件 redo log是固定大小的&#xff0c;是循环写的过程 有了redo log之后&#xff0c;innodb就可以保证即使数据库发…

数据存储介绍

数据存储对象包括数据流在加工过程中产生的临时文件或加工过程中需要查找的信息。数据以某种格式记录在计算机内部或外部存储介质上。数据存储要命名&#xff0c;这种命名要反映信息特征的组成含义。数据流反映了系统中流动的数据&#xff0c;表现出动态数据的特征&#xff1b;…

STM32个人笔记-电源管理

笔记来源于STM32F103VET6&#xff0c;野火指南者&#xff0c;中文参考手册&#xff0c;HAL库开发手册和b站的野火指南者视频。观看过好多次了&#xff0c;但往往理解得不够全面&#xff0c;现记下小笔记&#xff0c;用来回顾。属于个人笔记。 电源监控器 STM32芯片主要通过VDD…

代码审计基础之SQL注入漏洞

1.SQL注入原理 SQL注入就是攻击者通过把恶意的SQL语句插入到Web表单的输入页面中&#xff0c;且插入的恶意语句会导致原有的SQL语句发生改变&#xff0c;从而达到攻击者的目的去让它执行一些危险的数据操作&#xff0c;进一步欺骗服务器去执行一些非本意的操作。 简单来讲&am…

Python BeautifulSoup4 入门使用

一、简介 BeautifulSoup4 与 lxml 一样&#xff0c;是一个 html 解析器&#xff0c;主要功能也是解析和提取数据。 BeautifulSoup4 是 爬虫 必学的技能。BeautifulSoup 最主要的功能是从网页抓取数据&#xff0c;Beautiful Soup 自动将输入文档转换为 Unicode 编码&#xff0c…

Verilog语言中case、casex、casez的用法和区别

casez与casex语句是case语句的两种变体, 在写testbench时用到。case 语句是一种多路条件分支的形式&#xff0c;可以解决 if 语句中有多个条件选项时使用不方便的问题。 一、case、casex、casez的区别 下表给出case、casex、casez的真值表&#xff1a; 1&#xff09;在case语…

【计算机网络实验】防火墙访问控制列表实验

实验内容 防火墙访问控制列表实验 实验目的 理解访问控制列表的工作原理&#xff1b;了解访问控制列表的类型&#xff1b;学习标准访问控制列表的配置。 实验要求 1 实验拓扑图 本实验所用的网络拓扑如图1所示。 图1 ACL实验拓扑结构 2 实验步骤 Router0配置&#xff1b;&…

解决 npm install express 遇到的问题总结

方法1&#xff1a;权限 以管理员身份运行cmd执行npm install express --save命令 方法2&#xff1a;切换镜像源 查看镜像源 npm config get registry 如果要直接更换淘宝&#xff1a;npm config set registry https://registry.npmmirror.com/ 使用nrm切换 1.安装nrm npm i …

106362-34-9,(D-Ala1)-Peptide T amide

肽t的有效类似物DAPTA (aSTTTNYT-amide)在单核/巨噬细胞中显示出很强的抗hiv - 1活性&#xff0c;该肽抑制病毒的进入。 编号: 110545中文名称: 肽T、(D-Ala1)-Peptide T amide英文名: (D-Ala1)-Peptide T amideCAS号: 106362-34-9单字母: H2N-DAla-STTTNYT-NH2三字母: H2N-DAl…

设计模式 — 抽象工厂模式

抽象工厂模式女娲的失误实例 一实例 二抽象工厂模式的应用抽象工厂模式的优点抽象工厂模式的缺点抽象工厂模式的使用场景抽象工厂模式的注意事项女娲的失误 女娲造人的故事。人是造出来了&#xff0c;世界也热闹了&#xff0c;可是低头一看&#xff0c;都是清一色的类型&#…

Spark框架概述

Spark 框架概述 1.1. Spark是什么 定义&#xff1a;Apache Spark是用于大规模数据处理的统一分析引擎。 弹性分布式数据集RDD是一种分布式内存抽象&#xff0c;其使得程序员能够在大规模集群中做内存运算&#xff0c;并且有一定的容错方式。而这也是整个Spark的核心数据结构…

体验静态代码块

定义 public class Game {// 静态代码块static {System.out.println("static...run...");}// 构造方法public Game() {System.out.println("game...construct...");} }使用 结论 静态代码块在类被首次加载的时候触发启动

效能优化实践:C/C++单元测试万能插桩工具

研发效能是一个涉及面很广的话题&#xff0c;它涵盖了软件交付的整个生命周期&#xff0c;涉及产品、架构、开发、测试、运维&#xff0c;每个环节都可能影响顺畅、高质量地持续有效交付。在腾讯安全平台部实际研发与测试工作中我们发现&#xff0c;代码插桩隔离是单元测试工作…

theos tweak导入自定义类

有时&#xff0c;我们使用tweak的时候需要用到自定义的类&#xff0c;那么怎么引用呢&#xff1f; 假设我们有一个自定义类&#xff0c;people.h/people.m 那么分两种情况&#xff1a; 情况一&#xff0c;直接使用官方的tweak工程&#xff1a; 目录结构一般如下&#xff1a; …

[第九篇]——Docker 镜像使用

Docker 镜像使用 当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载。 下面我们来学习&#xff1a; 1、管理和使用本地 Docker 主机镜像2、创建镜像列出镜像列表 …

蛋白纯化-实验设计

小 M 不怕纯化“难”&#xff0c;IP、WB 只等闲。泡了两年实验室的小 M&#xff0c;理论与实操经验共有&#xff0c;且看我如何闯过蛋白纯化的几道“关”。 第一关 产品选择 小 M 敲黑板&#xff1a;此关最基础也最重要&#xff0c;谨防“一步错&#xff0c;步步错”。 亲和层析…

jenkins+junit4+allure+selenium实现自动化测试与结果可视化

安装包 jenkins.war jdk-8u332-linux-x64.tar.gz https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/2.17.2/ allure-commandline-2.17.2.zip https://chromedriver.storage.googleapis.com/index.html chromedriver 安装JDK 解压 tar xvf…