当前位置: 首页 > news >正文

JavaEE:synchronized关键字

目录

synchronized的特性

1)互斥

注意:

2)直接修饰普通方法

3)修饰静态方法

4)修饰代码块

锁当前对象:

锁类对象

​编辑

关于锁对象:

 5)可重入


synchronized的特性

1)互斥

多个线程如果同时针对同一个对象进行加锁时(进入synchronized修饰的代码块相当于加锁,退出synchronized修饰的代码块相当于解锁),会发生“锁竞争”,但只有一个线程(先进行加锁操作的)能够加锁成功,其他线程就会阻塞等待


    进入 synchronized 修饰的代码块, 相当于 加锁
    退出 synchronized 修饰的代码块, 相当于 解锁

 

就比如说我们在追女神的时候,有好几个哥么一起追,但是不会说我先追的女神,女神就必须当我的女朋友,这是要去竞争的(这就相当于锁竞争),当有一个兄弟追到了女神,我们只能眼睁睁看着(阻塞等待),等到女神和这个兄弟分手后再次竞争(锁竞争),并不会遵循先来后到的规则

 但是如果多个线程针对不同的对象进行加锁时,不会发生锁竞争:线程1针对A对象加锁,线程2针对B对象加锁,此时不会发生“锁竞争”,也就不会产生阻塞等待。好比几个兄弟各自使用不同的坑位

加锁需要考虑好锁那段代码,锁的代码范围不一样,对代码执行的效果会有很大的影响,锁的代码越多,就叫做"锁定粒度越大/越粗" ; 锁的代码越少,就叫做"锁的粒度越小/越细"

注意:

在这里我们提出一共疑问:当我们需要用到两个线程时,一个线程加锁,一个线程不加锁,这个时候会咋样?线程安全吗?

注意:线程安全,不是加了锁就一定安全.而是通过加锁,让并发修改同一个变量=>串行修改同一个变量,才安全的 ,不正确的加锁姿势,不一定能解决线程安全问题!!!!

class Count{
    public int count = 0;
    public synchronized void increase(){
        for (int i = 0; i < 50000; i++) {
            count++;
        }
    }
    public void increase2(){
        for (int i = 0; i < 50000; i++) {
            count++;
        }
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Count c = new Count();
        Thread t1 = new Thread(() -> {
           c.increase();
        });
        Thread t2 = new Thread(() -> {
           c.increase2();
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("count = " + c.count);
    }
}

 

如果只给一个线程加锁,这个是没啥用的.一个线程加锁,不涉及到"锁竞争",也就不会阻塞等待也就不会并发修改=>串行修改

好比A追到了妹子~~B也想追妹子.虽然A官宣了(加锁了),但是B不讲武德,搞偷袭,搞挖墙脚~~(只要锄头挥得好,没有墙角挖不倒)

2)直接修饰普通方法

synchronized修饰普通成员方法时,被加锁的对象(即锁对象)为当前对象本身,相当于this

public class Test {
    public static int count;
    //synchronized修饰普通成员方法
    public synchronized void increase(){
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        //让两个线程同时进行50000次count++操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.increase();
            }
        });
        t1.start();
        t2.start();
 
        t1.join();
        t2.join();
 
        System.out.println("count = " + Test.count);
    }
}

一个线程在调用increase方法进行count++操作时,另一个线程只能阻塞等待,这样就相当于把count++这个不是原子的操作,打包成一个原子操作,从而使count得到正确的结果:

 

具体操作就如同这张图中展示的,当一个线程在进行操作时先加锁了,另一个线程阻塞等待,直到上一个线程释放锁时再执行

3)修饰静态方法

 

4)修饰代码块

锁当前对象:

锁类对象

关于锁对象:

明确指定锁哪个对象:在Java当中,任意的对象,都可以作为锁对象

关于synchronized(),的()的里面咱可以有:

等等...

这里的synchronized(),()里面要填的就是->你要针对哪个对象加锁!!(被用来加锁的对象,就简称为"锁对象")

使用锁的时候,一定要明确,当前是针对哪个对象加锁!!,very关键!!

咱们写多线程的时候,不关心这个锁对象究竟是谁,是那种形态,咱们只关心,多个线程是否锁的是同一个对象,是锁同一个对象,是锁同一个对象就有竞争,不同对象就无竞争

在大部分情况下,咱们可以直接在()中写this,但是在多线程代码中,切忌"无脑写",在大部分情况下是可以直接写this 的,但是具体还是看你的实际需求,希望在那些场景下产生竞争,哪些场景下不产生竞争,锁的对象是不同的!!!

 5)可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

但是一个线程针对同一把锁加锁两次,就可能造成死锁!

public synchronized void func(){
    synchronized (this){
        count++;
    }
}

第一次加锁,加锁成功,第二次加锁时第一次加的锁还没有释放,阻塞等待

就相当于:第一把锁需要完成锁内的任务才能释放锁,而他的任务包含了第二把锁,而第二把锁需要等待到第一把锁释放锁才能继续完成任务,这就形成了"死锁",这样的锁就称为:"不可重入锁"

Java 中的 synchronized 是 可重入锁, 因此没有上面的问题

在可重入锁的内部,包含“线程持有者”和“计数器”两个信息:

线程持有者:记录第一次加锁的线程,如果该线程在释放第一次加的锁之前再次进行加锁,那么仍然可以加锁成功,并让计数器自增一次。

计数器:加锁时自增一次,解锁时自减一次,只有当计数器递减为0的时候,线程进行解锁操作才会真正释放锁(其他线程才能获取到这个锁)。
 

相关文章:

  • 全开源二次元风格发卡
  • 时序分析 45 -- 时序数据转为空间数据 (四) 格拉姆角场 python 实践 (下)
  • qmake *.prf文件 自定义features
  • 深度学习目标检测入门论文合集讲解
  • c++11 日期和时间工具(std::chrono::duration)(二)
  • DataStructure_树的基本性质(m叉树和二叉树)
  • Flask 学习-76.Flask-RESTX 处理异常@api.errorhandler
  • Java Boolean类中booleanValue()方法具有什么功能呢?
  • c# 与stm32之间结构体的收发
  • java集合专题Map接口及HashMap/Hashtable/Properties使用方法底层结构及源码分析
  • Vue(六)——vuex
  • JavaScript 学习-47.export 和 import 的使用
  • Kafka 生产者
  • Spring核心IOC的核心类解析
  • 【数据挖掘】恒生金融有限公司2023届秋招数据ETL工程师笔试题解析
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • [译]Python中的类属性与实例属性的区别
  • 30天自制操作系统-2
  • Golang-长连接-状态推送
  • JavaScript-Array类型
  • Mac转Windows的拯救指南
  • maya建模与骨骼动画快速实现人工鱼
  • Python3爬取英雄联盟英雄皮肤大图
  • Python利用正则抓取网页内容保存到本地
  • spring-boot List转Page
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • Wamp集成环境 添加PHP的新版本
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 开源SQL-on-Hadoop系统一览
  • 聊聊sentinel的DegradeSlot
  • 前嗅ForeSpider采集配置界面介绍
  • 微信小程序设置上一页数据
  • 一道闭包题引发的思考
  • 1.Ext JS 建立web开发工程
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #HarmonyOS:Web组件的使用
  • #ifdef 的技巧用法
  • (16)Reactor的测试——响应式Spring的道法术器
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (多级缓存)多级缓存
  • (二)pulsar安装在独立的docker中,python测试
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (三)elasticsearch 源码之启动流程分析
  • (一)VirtualBox安装增强功能
  • (转)原始图像数据和PDF中的图像数据
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • ***原理与防范
  • .net core 6 redis操作类
  • .net framework4与其client profile版本的区别
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET单元测试