Java中synchronized关键字到底怎么用,这个例子一定要看!

news2025/4/18 4:09:52

在平时开发中,synchronized关键字经常遇到,你知道synchronized怎么用吗?本文给大家介绍一下。

我们有两种方法使用同步:

  • 使用同步方法
  • 使用同步语句或块

使用同步方法

要使方法同步,只需将synchronized关键字添加到其声明中:

public class SynchronizedDemo {

    private int i = 0;

    public synchronized void add() {
        i++;
    }

    public synchronized void del() {
        i--;
    }

    public synchronized int getValue() {
        return i;
    }
}
复制代码

如上代码显示,一共有三个同步方法:

  • add()
  • del()
  • getValue()

每个方法同一个对象同一时刻只会被调用一次,比如一个线程在调用add()时,其他线程都会被阻塞,直到第一个线程处理完add()方法。

使用同步语句或块

    public void del(int value){

        synchronized(this){
            this.i -= value;
        }
    }
复制代码

如上代码,synchronized加在了一个{}代码前,这个就代表是一个同步代码块。

以上就是synchronized关键字两种使用方法,下面我们来简单的介绍一下同步相关的概念。

什么是同步?

同步是一个控制多个线程访问任何共享资源的进程,可以避免不一致的结果。使用同步的主要目的是避免线程的不一致行为,防止线程干扰。

在java中可以使用synchronized 关键字实现同步的效果,synchronized只能应用于方法和块,不能应用于变量和类。

为啥需要同步?

首先我们来看一段代码:

public class SynchronizedDemo {

    int i;

    public void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        synchronizedDemo.increment();
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

每当调用increment()方法时计算值都会加1:

调用2次就会加2,调用3次就会加3,调用4次就会加4:

public class SynchronizedDemo {

    int i;

    public void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        synchronizedDemo.increment();
        synchronizedDemo.increment();
        synchronizedDemo.increment();
        synchronizedDemo.increment();
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

现在我们扩展一下上面的例子,创建一个线程去调用10次increment()方法:

public class SynchronizedDemo {

    int i;

    public void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                synchronizedDemo.increment();
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

此时计算的结果正如我们预料的那样,结果为10.

这是单线程的情况,一切都是如此的美好,但是事实真的如此吗?如果是多线程环境,会是什么样的?

下面我们来演示一下多线程的情况!

public class SynchronizedDemo {

    int i;

    public void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();

        Thread thread1 = new Thread(() -> {
            for (int i = 1; i <= 1000; i++) {
                synchronizedDemo.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i <= 1000; i++) {
                synchronizedDemo.increment();
            }
        });

        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

如上代码,我们创建了两个线程 thread1 和 thread2,每个线程调用1000次increment(),理论上最终打印的值应该是2000,因为thread1调用increment()1000次后值会变成1000,thread2调用increment()1000次后值会变成2000.

我们执行一下,看看结果:

结果和我们想的不一样,小于2000,我们再执行一下:

结果还是小于2000.

这是为什么呢?

因为多线程支持并行处理,因此,两个线程总是有可能同时获取计数器的值,因此都得到相同的计数器值,所以在这种情况下,不是递增计数器的值两次,只增加一次。

那么,如何避免这种情况呢?

使用 synchronized 关键字即可解决。

我们只需要将increment()方法加上synchronized就可以了:

public class SynchronizedDemo {

    int i;

    public synchronized void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();

        Thread thread1 = new Thread(() -> {
            for (int i = 1; i <= 1000; i++) {
                synchronizedDemo.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i <= 1000; i++) {
                synchronizedDemo.increment();
            }
        });

        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

这个时候我们再执行一下:

可以看到,值为2000.

我们把计算次数提高到10000次:

public class SynchronizedDemo {

    int i;

    public synchronized void increment() {
        i++;
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();

        Thread thread1 = new Thread(() -> {
            for (int i = 1; i <= 10000; i++) {
                synchronizedDemo.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i <= 10000; i++) {
                synchronizedDemo.increment();
            }
        });

        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("计算值为:" + synchronizedDemo.i);
    }
}
复制代码

执行结果为:

可以看出,一个小小的synchronized竟然那么简单的就解决了这个问题。

这个背后的原理就是线程1执行increment()方法时,因为有synchronized,所以会自动将此方法加锁,而此时只有线程1拥有这把锁,其他线程只能等待,直到线程1释放这把锁,线程2才能参与调用。

同理,当线程2去调用increment()时,线程2拿到锁,线程1进入等待,直到线程2释放锁,就这样,直到计算完毕,在此过程中,不会出现计算错误的情况。

总结

  • synchronized 关键字是使块或方法同步的唯一方法。
  • synchronized 关键字提供了锁的特性,它确保线程之间不会出现竞争条件。被锁定后,线程只能从主存中读取数据,读取数据后,它会刷新写操作,然后才能释放锁。
  • synchronized 关键字还有助于避免程序语句的重新排序。

以上三个特性便是synchronized 关键字的精华中的精华,请大家牢记!

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

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

相关文章

用javascript分类刷leetcode3.动态规划(图文视频讲解)

什么是动态规划 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;将问题分解为互相重叠的子问题&#xff0c;通过反复求解子问题来解决原问题就是动态规划&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划来解是比较…

测量温度的优选模块——新型设备M-THERMO3 16

| 具有16个自由选择通道的新型温度测量设备M-THERMO3 16 IPETRONIK推出了新的温度测量设备——M-THERMO3 16。作为新一代设备的首个模块&#xff0c;它为模块化测量技术确立了标准。该模块具有16个通道&#xff0c;并且各通道不仅分别率高达24位ADC&#xff0c;而且可以自由选…

git初学(二)

git如何进行版本控制&#xff1f; 如何做版本控制呢&#xff1f;其实就是让git管理文件夹&#xff0c;比如我们有一个项目叫学生考试管理系统&#xff0c;首先我们创建一个文件夹student&#xff0c;这个文件夹当中保存所开发的所有代码 进入要管理的文件夹 初始化 git ini…

FTX创始人被警方逮捕:身价曾超150亿美元 坑惨红杉

雷递网 雷建平 12月13日“加密货币大王”、加密货币交易所FTX创始人、前首席执行官&#xff08;CEO&#xff09;萨姆班克曼-弗里德&#xff08;Sam Bankman-Fried&#xff0c; 简称“SBF”&#xff09;日前在巴哈马被逮捕&#xff0c;可能被引渡至美国。巴哈马总检察长办公室和…

访问者模式

一、访问者模式 1、定义 访问者模式&#xff08;Visitor Pattern&#xff09;是一种将数据结构与数据操作分离的设计模式&#xff0c;指封装一些作用于某种数据结构中的各元素的操作&#xff0c;可以在不改变数据结构的前提下定义作用于这些元素的新的操作&#xff0c;属于行为…

漫画 | 这个北欧小国发明的编程技术,竟然占领全世界了!

上世纪60年代 &#xff0c;挪威计算中心。一个新来员工刚上班&#xff0c;发现有两个人居然在一楼的黑板前打架新员工立刻跑到电话接线员那里报告什么样的编程语言&#xff0c;能让两人大动肝火&#xff1f;当时Nygaard正在编写复杂系统的模拟程序&#xff0c;模拟程序要求先定…

【猿如意】中的『XMind』工具详情介绍

目录 一、工具名称 二、下载安装渠道 2.1 什么是猿如意&#xff1f; 2.2 如何下载猿如意&#xff1f; 2.3 如何在猿如意中下载开发工具&#xff1f; 三、XMind工具功能简介 四、XMind的下载和安装 4.1下载 4.2安装 五、XMind的基本使用 5.1新建项目 5.2系统模板的使…

【java】程序员基础能力测试33问,持续整理中

Java基础&#xff1a; 1&#xff1a;八大基本数据类型&#xff0c;及所占字节数&#xff1f; 2&#xff1a;讲下对面向对象的理解&#xff1f; 特征:封装、继承、多态; 基础:抽象 面向对象&#xff0c;主要就是将现实中的对象抽象成一个类&#xff0c;这个对象具有一定的属性…

[附源码]Python计算机毕业设计服装商城平台Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

项目管理之Git---submodule

0. 简介 在面对复杂系统时&#xff0c;所有的模块不可能同时开发在一个project下的&#xff0c;而更多的可能就是每个人开发不同的模块&#xff0c;并通过一个模块将这些模块都整合到一起&#xff0c;这时候submodule的作用就非常明显了。通过设置submodule可以轻易地对不同的…

通信基站安装步骤

基站设备安装步骤(移动通信基站施工过程),并说明施工要点和注意事项。 安装机柜流程。安装电源线和系统接地。 安装电源机柜时应直流配电柜接出-48V直流电源至RNC810机柜和NodeB机柜顶端配线盒。 将保护地线接至机柜接地螺栓上并紧固螺栓。 天馈系统安装。 天馈系统安装前的…

大数据学习:压缩与打包

文章目录任务一&#xff1a;压缩文件任务二&#xff1a;解压文件任务三&#xff1a;生成打包文件任务四&#xff1a;将打包文件解压到当前文件任务五&#xff1a;将打包的文件解压缩到指定目录任务六&#xff1a;解压打包文件里的某个目录任务一&#xff1a;压缩文件 在/tmp目…

redis地理位置和MongoDB地理索引的使用

比较 经度纬度都要在有效区间。经度范围介于 -180 到 180&#xff0c;纬度范围大致介于-90和到90。redis使用Zset结构存储&#xff0c;将经度值、纬度值转换为一个值&#xff0c;二维量变成一维量找附近的位置&#xff0c;效率极高&#xff0c;不过限于平面&#xff0c;且无法…

测量学:水准和导线测量实验报告+详细解析

目录 00 说明 实验1 闭合导线测量 实习目的 实习任务和内容 控制点的布置和测量技术要求&#xff08;绘制导线略图&#xff09; 导线略图 外业测量数据和记录相关表格&#xff08;附原始观测记录&#xff09; 原始观测数据记录如下&#xff1a; 记录表格如下&#xff…

Web3中文|星巴克拥抱Web3,新项目Odyssey开启数字旅程

12月8日&#xff0c;成立用于1971年&#xff0c;全球82个市场拥有超过32,000家门店的美国咖啡公司星巴克对其备受期待的Odyssey体验进行了测试&#xff0c;该体验将客户忠诚度奖励与NFT以及其他游戏元素相结合。 早在9月12日&#xff0c;星巴克宣布将推出Web3平台“Starbucks …

openEuler社区开源项目:CPDS(容器故障检测系统)介绍

容器故障检测系统 CPDS (Container Problem Detect System) 是由北京凝思软件股份有限公司&#xff08;以下简称“凝思软件”&#xff09;设计并开发的容器集群故障检测系统&#xff0c;该软件系统实现了对容器TOP故障、亚健康状态的监测与识别。 2022年11月&#xff0c;凝思软…

isp,iap,sw-jtag

https://blog.csdn.net/weixin_45905650/article/details/107707858?ops_request_misc%257B%2522request%255Fid%2522%253A%2522167098526816800180634199%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id167098526816800180634199&biz_i…

JS:通过setTimeout和promise来了解js代码执行机制(面试题讲解)

目录 1.setTimeout定时器 2.promise函数 补充&#xff1a;1.什么是宏任务与微任务 补充&#xff1a;2.宏任务和微任务的执行顺序 补充&#xff1a;3.js的执行顺序 补充&#xff1a;4.答案揭晓 前几天碰到一个关于js执行顺序的面试题&#xff0c;一时间竟然有点模糊&#…

BCN-PEG-Folate 环丙烷环辛炔聚乙二醇叶酸 BCN-PEG-FA

双环[6,1,0]壬炔 (BCN) &#xff08;环丙烷环辛炔&#xff09;可以通过无铜的点击化学与叠氮化物标记的分子或生物分子反应生成稳定的三氮唑连接。 产品名称 BCN-PEG-Folate 环丙烷环辛炔聚乙二醇叶酸 中文名称 环丙烷环辛炔聚乙二醇叶酸 英文名称 BCN-PEG-Folate BCN…

统信软件高级系统研发工程师:sysOM 在系统可靠性与安全上实践

一、系统可靠性 SRE是判断系统是否可靠、可用、有效重要标准&#xff0c;它包括&#xff1a; 服务水平指标SLI&#xff1a;衡量服务使用情况量化指标。 比如IO读写速率、网络延迟。通常量化指标会转换为比率、平均值或百分比。服务水平目标SLO&#xff1a;一段时间、区间内的目…