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

synchronized到底锁的是谁、何时生效

一、synchronized锁的几种形式

  1. synchronized修饰普通方法
  2. synchronized修饰静态方法
  3. synchronized修饰代码块

1、synchronized修饰普通方法

简单示例

public class Test{

    private String age;
    private String name;

    public synchronized void print(String arg1, String arg2) {
        this.age = this.age + arg1;
        this.name = this.name + arg2;
    }
}

下面看一个稍微复杂的场景,main方法中启动两个线程,func1和func2均被synchronized修饰。
由于是非静态方法,锁的是当前对象data,由于func1和func2方法都被synchronized修饰,且在main方法中,是用的new出来的同一个data进行调用,于是锁的就是这个data,用到data发生资源抢占,需要排队。代码首先执行到func1,func1执行3秒,func2排队等待三秒后,执行func2。

public class Test{

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(data::func1).start();
        new Thread(data::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public synchronized void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

但如果把func2方法的synchronized修饰去掉,那么运行结果如下。
因为去掉synchronized的func2不再参与data资源的抢占,main方法启动后,func的线程和func2的线程同时启动,首先到达线程1,但因为需要等待3秒,于是func2的结果率先输出,3秒后再输出func1的结果

public class Test{

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(data::func1).start();
        new Thread(data::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

此时再做一个变化,func1和func2仍然使用synchronized修饰,但main方法中调用func1与func2每次都是新new出来的Data对象,因为普通方法锁定的是同一个对象,而不同的对象直接不发生资源抢占,因此func2无需等待func1执行完毕再执行,而是和func1同步执行。

public class Test{

    public static void main(String[] args) {
        new Thread(new Data()::func1).start();
        new Thread(new Data()::func2).start();
    }
}

class Data{

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized void func1(){
        System.out.println("func1开始执行,当前时间"+ sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间"+sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间"+sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间"+ sbf.format(new Date()));
    }

    public synchronized void func2(){
        System.out.println("func2开始执行,当前时间"+ sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间"+ sbf.format(new Date()));
    }
}

在这里插入图片描述

2. synchronized修饰静态方法

简单示例

public class Test {

    public synchronized static void print(String arg1, String arg2) {
        System.out.println("静态方法锁");
    }
}

继续使用标题1中的例子,将func1及func2都修改为静态方法,然后在main方法中,new出来两个data对象分别调用,看下面的执行结果,发现虽然是两个对象,但仍然发生了资源抢占,因此synchronized修饰静态方法,锁定是当前类而不是当前对象。

同样的,去掉其中一个方法的synchronized修饰,则不会进行排队。

public class Test {

    public static void main(String[] args)  {
        new Thread(()->new Data().func1()).start();
        new Thread(()->new Data().func2()).start();
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public synchronized static void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()));
        try {
            System.out.println("func1休息3秒,当前时间" + sbf.format(new Date()));
            Thread.sleep(3000);
            System.out.println("func1休息3秒结束,当前时间" + sbf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func1:1111111111111111111111111111111111111111111111111");
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()));
    }

    public synchronized static void func2() {
        System.out.println("func2开始执行,当前时间" + sbf.format(new Date()));
        System.out.println("func2:22222222222222222222222222222222222222222222222222");
        System.out.println("func2执行结束,当前时间" + sbf.format(new Date()));
    }
}

在这里插入图片描述

3. synchronized修饰代码块

简单示例

public class Test{

    private String age;
    private String name;

    private Object object = new Object();
    
    public void print(String arg1, String arg2) {
        synchronized (object) {
            this.age = this.age + arg1;
        }
        this.name = this.name + arg2;
    }
}

继续用标题1中的例子修改,起5个线程,去调用func1,func1调用func2,且将func2包裹在同步代码块中,用synchronized修改,锁定this对象,而this指的就是当前Data对象的实例化对象,因此五个线程均发生了资源抢占,挨个按顺序执行。synchronized同步代码块可圈定最小的锁范围,提高效率。

public class Test {

    public static void main(String[] args) {
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定this对象
        synchronized (this) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
同样的,若将main方法中调用func1的对象,每次都新new出来一个采用不同的对象,则不会进行排队。

public class Test {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            // 每次都是不同的对象
            new Thread(new Data()::func1).start();
        }
    }
}

class Data {

    static SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定this对象
        synchronized (this) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
但将锁定对象换为new InstanceTest(),也没有触发排队,因为每次锁的对象都是新new出来的,锁的不是同一个,虽然调用的对象是同一个。

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new InstanceTest()对象
        synchronized (new InstanceTest()) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定对象InstanceTest移到func1方法外面变成Data类的成员变量:private InstanceTest instanceTest = new InstanceTest();,调用仍然是同一个data对象,因此Data类中new出来的InstanceTest也是同一个对象,因此会发生排队。

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    private InstanceTest instanceTest = new InstanceTest();

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定instanceTest对象
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定的对象换为Integer xxx = 1;因为Integer拆箱共享常量池,只有一份,则发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定Integer instanceTest = 1对象
        Integer instanceTest = 1;
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
但如果将Integer new出来对象,产生了多个不同的Integer对象,就不会排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new Integer()对象
        Integer instanceTest = new Integer(1);
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述
将锁定的Integer instanceTest = 1;修改为Integer instanceTest = 128;不发生排队。因为jdk自动拆箱,–128到127之间的值在内存中会被重复利用,认为是一个对象,而超过这个范围的值将会被加载为多个对象,每来一次new一个Integer。因此未发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new InstanceTest()对象
        Integer instanceTest = 128;
        synchronized (instanceTest) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述

此时我锁类。因为类只有一个,因此会发生排队

public class Test {

    public static void main(String[] args) {
        // 调用是同一个对象
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(data::func1).start();
        }
    }
}

class Data {

    SimpleDateFormat sbf = new SimpleDateFormat("HH:mm:ss");

    public void func1() {
        System.out.println("func1开始执行,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
        System.out.println("在func2执行之前先做点不需要排队没有并发问题的事情" + ",当前线程" + Thread.currentThread());
        // 此处锁定new Data()对象
        synchronized (Data.class) {
            func2();
        }
        System.out.println("func1执行结束,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }

    public void func2() {
        try {
            // 休息三秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("func2:22222222222222222222222222222222222222222222222222,当前时间" + sbf.format(new Date()) + ",当前线程" + Thread.currentThread());
    }
}

在这里插入图片描述

二、结论

1. synchronized修饰普通方法

锁定的是当前对象,当前方法的调用对象若是new出来的多份,则不发生资源抢占;只有同一个对象调用该方法时,synchronized锁才生效。

2. synchronized修饰静态方法

锁定是当前类,不论是用类调用的该方法,还是用new出来的一个或N个对象调用的该方法,都会发生资源抢占。

3. synchronized修饰代码块

锁定的是括号中的对象或类。可以认为是结合了1与2的优点与方式。括号中的对象若是一份,则synchronized锁生效,否则无效。生效情况如下:

  • 括号中的对象为this,且调用该方法的对象为同一个
  • 不论调用该方法的对象是不是同一个,括号中的对象永远是同一个,比如:Integer xxx = 1;
  • 括号中为类

相关文章:

  • Pytorch入门基础知识(一)
  • C#通过dll调用带参数的C++代码
  • 【C++】一文带你吃透string的模拟实现 (万字详解)
  • 融合transformer和对抗学习的多变量时间序列异常检测算法TranAD论文和代码解读...
  • Hdfs梳理
  • 智能小车 - DRV8833电机驱动模块
  • Spring常用注解——@Autowired自动装配的作用和原理
  • 一些运维命令
  • 代码随想录动态规划——背包问题总结篇
  • web安全之信息收集
  • 基于FPGA的双目相机目标深度图像提取实现——详细版
  • 【饭谈】细嗦那些职场中喜欢用领导口气命令别人的同事
  • 10 通用同步异步收发器(USART)
  • AI绘图—对中文拟合度很高,值得一试
  • 【 C++11 】包装器
  • [译]CSS 居中(Center)方法大合集
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • angular2 简述
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • js操作时间(持续更新)
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • node和express搭建代理服务器(源码)
  • PHP 7 修改了什么呢 -- 2
  • 工作中总结前端开发流程--vue项目
  • 聊聊flink的TableFactory
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • Hibernate主键生成策略及选择
  • Spring Batch JSON 支持
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #NOIP 2014# day.2 T2 寻找道路
  • (0)Nginx 功能特性
  • (3)nginx 配置(nginx.conf)
  • (4)(4.6) Triducer
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (zt)最盛行的警世狂言(爆笑)
  • (超详细)语音信号处理之特征提取
  • (二)构建dubbo分布式平台-平台功能导图
  • (强烈推荐)移动端音视频从零到上手(下)
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)EXC_BREAKPOINT僵尸错误
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • ***通过什么方式***网吧
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET程序员迈向卓越的必由之路
  • .NET框架设计—常被忽视的C#设计技巧
  • ?.的用法
  • [ C++ ] STL---string类的使用指南
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [Docker]十二.Docker consul集群搭建、微服务部署,Consul集群+Swarm集群部署微服务实战