synchronized方法和代码块

2023-11-07

1. 同步

由于多线程并发存在数据不安全问题,为了保证数据的安全性需要一些特殊的手段来维持。数据不安全主要是针对修改来说的,如果一个数据只能读不能修改几乎不会产生什么安全问题。只有修改数据的时候容易产生一些差错导致多线程并发造成数据不安全。

  • 从操作系统的角度来看多线程访问临界资源时并且对临界资源做一定的修改就会产生错误,操作系统为了保证临界资源的安全性,保证每次访问临界资源的线程有且只有一个。多线程访问时会进入等待队列依次排队等待。

  • Java多线程并发出现数据不安全问题的原因也大致相同,因为某些操作不是原子性的(可以拆开),例如 i++ 这种操作在CPU看来是比较麻烦的,首先要从内存中将数据i的值读入到当前线程的工作缓存工作区中,然后再将这个值+1。最后才是将值回写到内存中。至少是3步才能完成这样一个操作,因为并发是无法预料这个线程能否在被迫剥夺CPU时间之前完成这个操作有可能在回写值到内存中时此时还没回写完就已经被其他线程剥夺了CPU,那么回写失败之后的线程再去读这个值读到的还是原来的值并没有+1。

  • 为了保证这种上述操作能够完成,必须将三步操作看做一个整体执行时不能中断。只有完成回写之后其他线程才能去继续修改或者读取,这样就能保证正确性。


2. synchronized修饰词

Java中为了保证多线程并发的数据安全性提供了Synchronized关键字来保证。保证非原子操作执行时不能中断,同时多个线程修改同一个临界资源时必须采用串行执行,并且必须保证前一个线程执行完后一个线程才有机会去访问。就像卫生间一样,一旦从里面上了锁只有里面的人用完了打开了门外面的人才有机会进去。否则就有可能出现两个人同时出现在卫生间中… 所以可以将synchronized看做卫生间的门锁。


  • synchronized修饰非静态方法,这种修饰普通方法的实际上锁的是实例化的对象。
  1. 例如有一个对象test,派生出两个子线程A 和 B。这是采用静态代理的方式,那么sum是A和B共享的数据,为了保证安全给cnt方法上锁就可以保证安全。大概就是下面这样一个图。当A访问cnt方法时对象test上锁了,B就不能访问test所有的synchronized方法只有当A访问完毕打开锁B才能访问,但是B全程可以访问test的非synchronized方法。
    在这里插入图片描述
class TestA implements Runnable{
    public int sum = 0;     
    
    public synchronized void cnt(){		
        sum++;
    }

    @Override
    public void run() {
        for(int i = 0;i < 100000;i++){
            cnt();
        }
    }
}


public class FunSyn1{
    public static void main(String[] args) throws InterruptedException {
        TestA test = new TestA();
        Thread t1 = new Thread(test,"A");		//静态代理1
        Thread t2 = new Thread(test,"B");		//静态代理2
        t1.start();     t2.start();
        Thread.sleep(5000);				//主线程休眠5秒,让t1 和 t2先跑完
        System.out.println(test.sum);           //200000
    }
}


  1. 如果 A 是test1的线程,B 是 test2 的线程,很明显test1 和 test2 的数据sum是不共享的并发的时候互相不干扰(如下图情况1)。但是如果对象中的sum是一个static的变量,那么数据就是共享的了(如下图情况2)。但是现在如果对cnt方法加锁并不有效,因为锁的是对象test1 和 test2,只能保证线程A和线程B只有一个访问test1,以及一个线程C和线程D访问test2。但是并不能保证A、B、C、D四个线程每次只有一个线程去操作sum啊。(好好理解这句话),也就是可以有一个线程从test1访问sum,一个线程从test2访问sum又出现了两个线程一起访问修改一个变量啊!!!
    在这里插入图片描述
class TestB implements Runnable{
    public static int sum = 0;     //静态变量不属于某个具体的对象,是共有的。

    public synchronized void cnt(){
        sum++;
    }

    @Override
    public void run() {
        for(int i = 0;i < 100000;i++){
            cnt();
        }
    }
}


public class FunSyn2{
    public static void main(String[] args) throws InterruptedException {
        TestB test1 = new TestB();
        TestB test2 = new TestB();
        Thread t1 = new Thread(test1,"A");
        Thread t2 = new Thread(test2,"B");
        t1.start();     t2.start();
        Thread.sleep(5000);				//主线程休眠5秒,让t1 和 t2先跑完
        System.out.println(TestB.sum);           //小于200000
    }
}

小结:出现这种原因是因为加锁的对象不同,对象锁对于这种情况只能锁住从当前对象去访问的线程数目只有一个,但是并不能锁住所有的对象。也就是通向静态sum的路有多条,虽然能保证每条路上只有一辆车,但是我有多条路自然有多辆车可以通过不同的路到达sum。所以对于上面的情况2应该加锁的是sum变量或者是将通向sum的路抽象出来一条路并且这条路每次只有一个车能过,而不是对象上锁这样就能保证访问sum的每次只有一个线程



  • synchronized修饰静态方法,这个锁就是当前类的class对象锁。由于静态成员不属于任何一个实例对象是类成员,因此通过class对象锁可以控制静态成员的并发操作。这样就确保了通向sum的路只有一条并且这条路上每次只有一辆车。注意如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例化对象锁
    在这里插入图片描述
class TestC implements Runnable{
    static int sum = 0;

    public static synchronized void cnt(){
        sum++;
    }

    @Override
    public void run() {
        for(int i = 0;i < 100000;i++){
            cnt();
        }
    }
}

public class StaticSyn {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new TestC(),"A");
        Thread t2 = new Thread(new TestC(),"B");
        t1.start(); t2.start();
        Thread.sleep(5000);
        System.out.println(TestC.sum);              //200000
    }
}
  • synchronized修饰代码块,当需要编写的代码量较大并且只有少部分代码需要进行同步操作如果对方法进行同步可能会导致性能偏低,为了解决这个问题可以将这少部分需要同步的代码提取成一个同步代码块。
  1. synchronized代码块锁实例化对象,类似于synchronized修饰非静态方法

    class TestD implements Runnable{
        static int sum = 0;
        @Override
        public void run() {
            synchronized (this){            //对象锁
                for(int i = 0;i < 100000;i++){
                    sum++;
                }
            }
        }
    }
    public class SynBlockThis {
    
        public static void main(String[] args) throws InterruptedException {
            TestD test = new TestD();
            Thread t1 = new Thread(test,"A");
            Thread t2 = new Thread(test,"B");
            t1.start(); t2.start();
            Thread.sleep(5000);
            System.out.println(test.sum);           //200000
        }
    }
    

  1. synchronized代码块锁class类,类似于synchronized修饰静态方法

    class TestE implements Runnable{
        static int sum = 0;
        @Override
        public void run() {
            synchronized (SynBlockClass.class){         //类锁
                for(int i = 0;i < 100000;i++){
                    sum++;
                }
            }
        }
    }
    public class SynBlockClass{
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new TestE(),"A");
            Thread t2 = new Thread(new TestD(),"B");
            t1.start();     t2.start();
            Thread.sleep(5000);
            System.out.println(TestE.sum);				//200000
        }
    }
    

3. 总结

synchronized的用法大概就这几种情况但是变化很多,主要去判断应该锁对象还是锁类,怎么锁才是关键。但是synchronized并不是万能的,会有死锁的情况发生。

  1. 一个线程持有锁会导致其他需要此锁的线程挂起

  2. 多线程竞争下,加锁开锁会导致比较多的上下文切换 和 调度延时,引起性能问题。

  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

  4. 数据安全有了一定的保证,但是可能引起死锁。







本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

synchronized方法和代码块 的相关文章

随机推荐

  • 学习记录(C语言)

    一 初识转义字符 转义字符有哪些 其中 表示打印一个单独的单引号或者双引号 n表示换行 t相当于tab键 ddd ddd表示1 3个八进制数字例如打印 130 八进制的130相当于十进制的88再对照ASCII码表88对应的符号为X 同理 x
  • Python网络爬虫原理及实践

    作者 京东物流 田禹 1 网络爬虫 网络爬虫 是一种按照一定的规则 自动地抓取万维网信息的程序或者脚本 网络爬虫相关技术和框架繁多 针对场景的不同可以选择不同的网络爬虫技术 2 Scrapy框架 Python 2 1 Scrapy架构 2
  • windows 安装 yarn

    windows 安装 yarn 下载node js http nodejs cn download 通过 Chocolatey 安装 以管理员身份打开 cmd exe SystemRoot System32 WindowsPowerShel
  • 时间数组传到数据库

    1 数据库 有数据 想精确到时分秒 就写datatime 不想要就写data 2 vue页面 写一个数组 可以放 开始时间 结束时间 3 把dataRange数组的索引 0 赋给workTimeBegin 1 赋给workTimeEnd 这
  • 给exe添加管理员权限的几种方法&修改exe兼容性属性的方法

    有些程序我们必须以管理员权限才能运行 但是我们又不想 右键 gt 以管理员权限运行 那么我们就只有给exe增加管理员权限了 1 如果是vs编译的exe可以通过配置属性 项目 XXX 属性 配置属性 链接器 清单文件 然后重新编译 可以根据需
  • delphi .dfm文件汉字不显示_搞定Delphi模块化之Package

    Package的优点 应用程序可以被高度的模块化 而且可以逐渐交付完成的功能给客户 维护方便 可以只更新单一的模块功能 提升程序的载入速度 Package的缺点 有些情形下使用Package只能间接的方式取得资料 变量 类 Package
  • springboot项目上传文件过大导致java heap space 的问题解决方案(windows和linux)

    windows 点击项目 run as debug as Run Configuration Debug Configuration java application Arguments Xmx1024m Xms1024m XX NewRa
  • 万物云原生下的服务进化

    导读 在万物云原生下的环境下 Java的市场份额也因耗资源 启动慢等缺点 导致在云原生环境里被放大而降低 通过这篇文章 读者可以更好地了解如何在云原生环境下通过升级相关版本和使用GraalVM打出原生镜像到方式 优化Java应用的性能和资源
  • 【详细齐全】FIO使用方法 及参数解析(文章末尾)

    linux 使用FIO测试磁盘iops 方法详解 FIO是测试IOPS的非常好的工具 用来对硬件进行压力测试和验证 支持13种不同的I O引擎 包括 sync mmap libaio posixaio SG v3 splice null n
  • 安装fastdfs及java操作

    1 安装 libfastcommon git地址 https github com happyfish100 libfastcommon git 安装 git clone https github com happyfish100 libf
  • key-value数据库

    传统的文件系统中 需要维护目录的层次结构 使用dentry inode directory等复杂结构保存元数据的信息 而面对更多定制文件系统的需求 越来越多的系统考虑使用key value形式保存文件系统中的元数据信息 使用数据库来保存这些
  • Windows 安装完mysql 后 配置环境变量、重置密码、开启远程连接

    第一步 配置环境变量 系统变量 Path在最后一段添加 D ruanjian mysql 8 0 17 winx64 bin 这个是你安装mysql bin的位置 第二步用临时密码登录并重置密码 mysql gt mysql u root
  • yum默认安装位置及被占进程运行:/var/run/yum.pid 已被锁定,PID 为 4032 的另一个程序正在运行。

    yum install XX 默认安装位置是 usr lib64 在安装过程出现下面进程被占用 解决 rm f var run yum pid
  • 9个开源自动化测试框架,质量保证测试工程师用起来

    自动化测试框架由一组最佳实践 通用工具和库组成 可帮助测试人员评估多个Web和移动应用的功能 安全性 可用性和可访问性 而在 软件开发世界中有很多的自动化测试框架 该如何选择 虽然技术团队可以构建复杂的自动化测试框架 但是当可以选择现有的开
  • Git 补丁— diff 和 patch 使用详解

    Git 补丁 diff 和 patch 使用详解 1 diff 和 patch 的区别 2 生成patch 2 1 git diff 3 git format patch 3 应用patch 4 冲突解决 题外话 1 diff 和 patc
  • flutter 图片加载和预览实现

    注 本文只提供了思路 并无完整性demo 使用的是cached network image 因为版本变革经常头大 而且需要自己对源码做些修改 建议github fork一下 然后导入自己的地址 类似 dependencies cached
  • Spider(网络蜘蛛)之ajax爬取douban电影排行和kfc门店数据

    爬前注意 首先douban的接口请求方式是get简单一点 寻找规律可以爬取数据 kfc的接口方式是post data所需要parse urlencode data encode utf 8 需要编码 编码和解码是两个相反的过程 字节 lt
  • 分布式事务框架Seata

    分布式事务框架Seata sei达 一 分布式事务前言 1 数据库管理系统中事务 transaction 的四个特性 简称ACID 这种特性简称刚性事物 原子性 Atomicity 原子性是指事务是一个不可再分割的工作单元 事务中的操作要么
  • 特征选择--scikit-learn

    特征选择 Feature Selection choosing a subset of all the features the ones more informative 最终得到的特征选是原来特征的一个子集 特征选取是机器学习领域非常重
  • synchronized方法和代码块

    1 同步 由于多线程并发存在数据不安全问题 为了保证数据的安全性需要一些特殊的手段来维持 数据不安全主要是针对修改来说的 如果一个数据只能读不能修改几乎不会产生什么安全问题 只有修改数据的时候容易产生一些差错导致多线程并发造成数据不安全 从