Java synchronized关键字和锁分类

news/2025/2/2 6:01:04 标签: java, 开发语言

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  1. 关于synchronized已经有很多博主很细致的讲解了,本文主要是通过代码例子再次理解下,并要求能头脑反射出相关知识点
  2. 主要是锁分类的知识点掌握,方便平时工作中知晓用什么锁,细节则需要专门深入

目录

  • synchronized
    • 锁住什么
    • 保证线程安全
    • 锁升级
    • 相关题目
      • 附加测试代码,请每个case过一下
  • 锁分类

synchronized

提及synchronized需要想到如下的几个知识点

锁住什么

是对象,参考阅读ObjectMonitor相关知识:https://blog.csdn.net/qq_26437925/article/details/145400968

补充Java对象头之markword

markword共8个字节,64bit,包括:锁信息gc信息identity hashcode

在这里插入图片描述

保证线程安全

即原子性、可见性、有序性

  • synchronized 提供了一种锁对机制,能确保共享变量的互斥访问,从而防止数据不一致问题的出现

  • synchronized 包括了monitorentermonitorexit两个JVM指令(jvm层面则是C/C++调用了操作系统提供的同步机制,其是要依赖于硬件cpu的。CPU级别则是使用lock指令来实现的):它能确保在任何时候,任何线程执行到monitor enter成功之前都必须从主内存中获取数据,而不是从缓存中,在monitor exit运行成功之后,共享变量被更新后的值必须刷入主内存

  • synchronized 严格准守Java happends-before规则,一个monitor exit指令之前必定要有一个monitor enter

反编译查看(字节码层级的实现)

java">public class SynchronizedDemo {
    public void method (){
        synchronized (this) {
            System.out.println("method 1 start!!!!");
        }
    }
}
java">javac -encoding utf-8 SynchronizedDemo.java
javap -c SynchronizedDemo
java">Compiled from "SynchronizedDemo.java"
public class SynchronizedDemo {
  public SynchronizedDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void method();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3                  // String method 1 start!!!!
       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_1
      13: monitorexit
      14: goto          22
      17: astore_2
      18: aload_1
      19: monitorexit
      20: aload_2
      21: athrow
      22: return
    Exception table:
       from    to  target type
           4    14    17   any
          17    20    17   any
}

参考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14

锁升级

java">                【偏向锁】(匿名对象)
                /      \
对象new出来(无锁)         \
                \        \(只要有线程竞争,就会偏向-->轻量)
                  \       \
                    \      \
                      \     \
                      【轻量级锁】
                         \
                          \
                           \
                           【重量级锁】

偏向锁默认启动,会延迟启动(普通对象,有了偏向锁就是个匿名偏向)

轻量级锁,自旋锁,无锁:指向线程栈中Lock Record的指针(CAS操作)

重量级锁:操作系统层面,有竞争队列,等待队列(wait_set),不需要消耗CPU,后续操作系统调度

偏向锁 什么时候升级为 轻量级锁?

答:只要有线程竞争

轻量级锁 什么时候升级为 重量级锁?

答:JDK1.6之前:自旋次数10次;或者多个线程等待(超过CPU核心数的1/2) 就会发生升级;目前是JVM自适应自旋的升级

  • 轻量级锁:消耗CPU(用户态,不经过操作系统)

  • 重量级锁:不消耗CPU,有一个等待队列(阻塞); 涉及到用户态/内核态切换

相关题目

参考博主:橡 皮 人的一篇博文
https://blog.csdn.net/weixin_45433817/article/details/132216383

下面进行测试和一一说明,原文讲解的正确,但测试代码不友好

  1. 两个都是同步方法,先打印邮件还是短信?-------------先邮件再短信,共用一个对象锁。

例子代码:

java">import java.util.concurrent.TimeUnit;

class Phone1 {
    public synchronized void sendEmail() {
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        case1();
    }

    public static void case1() {
        Phone1 phone = new Phone1();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

}

这种情况是锁住对象实例,先执行的先获取到锁

  1. sendEmail()休眠3秒,先打印邮件还是短信?----------先邮件再短信,共用一个对象锁。
java">class Phone2 {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}
java">public static void case2() {
   Phone2 phone = new Phone2();
    new Thread(() -> {
        phone.sendEmail();
    }, "a").start();
    try {
        TimeUnit.MILLISECONDS.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(() -> {
        phone.sendSMS();
    }, "b").start();
}

仍然是对象锁,先获取到锁的先执行。虽然有sleep,但是sleep不会让出锁。所以会看先先停顿一会打印sendEmail, 然后是sendSms

  1. 添加一个普通的hello方法,先打印普通方法还是邮件?------先hello,再邮件。

这种自不必说,一个有锁一个无锁,无竞争关系,谁先执行到打印谁

  1. 两部手机,一个发短信,一个发邮件,先打印邮件还是短信?----先短信后邮件 资源没有争抢,不是同一个对象锁。
    在这里插入图片描述
    不同对象实例,各种的对象锁,所以a,b线程没有竞争关系, 虽然sendEmail先执行的但是有sleep后打印的

  2. 两个静态同步方法,一部手机,先打印邮件还是短信?-----先邮件再短信,共用一个类锁。

java">class Phone3 {
    public synchronized static void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        }
        System.out.println("------sendEmail");
    }

    public synchronized static void sendSMS() {
        System.out.println("------sendSMS");
    }
}

在这里插入图片描述

static方法用的是class对象锁,所以是有竞争关系的,先先获取到锁谁先执行,sleep不影响锁逻辑

  1. 两个静态同步,两部手机,一个发短信,一个发邮件,先打印邮件还是短信?-----先邮件后短信,共用一个类锁。

同5的解释

  1. 邮件静态同步,短信普通同步,先打印邮件还是短信?—先短信再邮件,一个类锁一个对象锁。

在这里插入图片描述

  1. 邮件静态同步,短信普通同步,两部手机,先打印邮件还是短信?------先短信后邮件,一个类锁一个对象锁。
java">class Phone5 {
    public synchronized static void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}

在这里插入图片描述

  • 静态方法用class对象的对象锁,普通方法用对象实例的对象锁
  • 二者没有竞争关系,谁先执行到打印方法就谁先打印

附加测试代码,请每个case过一下

java">import java.util.concurrent.TimeUnit;

class Phone1 {
    public synchronized void sendEmail() {
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }

    public void hello() {
        System.out.println("------hello");
    }

}

class Phone2 {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}

class Phone3 {
    public synchronized static void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        }
        System.out.println("------sendEmail");
    }

    public synchronized static void sendSMS() {
        System.out.println("------sendSMS");
    }
}

class Phone4 {
    public synchronized static void sendEmail() {
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}

class Phone5 {
    public synchronized static void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println("------sendSMS");
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        case6();
    }

    public static void case6() {
        Phone5 phone = new Phone5();
        new Thread(() -> { phone.sendEmail(); }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { phone.sendSMS(); }, "b").start();
    }

    public static void case5() {
        Phone4 phone = new Phone4();
        new Thread(() -> { phone.sendEmail(); }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { phone.sendSMS(); }, "b").start();
    }

    public static void case4() {
        Phone3 phone = new Phone3();
        new Thread(() -> { phone.sendEmail(); }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { phone.sendSMS(); }, "b").start();
    }

    public static void case3() {
        Phone2 phone21 = new Phone2();
        Phone2 phone22 = new Phone2();
        new Thread(() -> { phone21.sendEmail(); }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { phone22.sendSMS(); }, "b").start();
    }


    public static void case2() {
        Phone2 phone = new Phone2();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

    public static void case1() {
        Phone1 phone = new Phone1();
        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

}

锁分类

以下是可以看到的一些锁的概念:

  1. 可重入锁、不可重入锁
  2. 乐观锁、悲观锁
  3. 公平锁、非公平锁
  4. 共享锁、互斥锁(或拍它锁)
  5. 无锁、偏向锁、轻量级锁、重量级锁
  6. 自旋锁,自适应自旋锁
  7. 读写锁;写锁、悲观读锁和乐观读锁
  8. 分段锁(Striped Locking)

可参考腾讯云文章:https://cloud.tencent.com/developer/news/1847413


http://www.niftyadmin.cn/n/5839815.html

相关文章

3 Yarn

3 Yarn 1. yarn的架构和原理1.1 yarn的基本介绍和产生背景1.2 hadoop 1.0 和 hadoop 2.0 的区别1.3 yarn 集群的架构和工作原理1.4 yarn 的任务提交流程 2. RM和NM的功能介绍2.1 resourceManager基本介绍2.2 nodeManager功能介绍 3. yarn的applicationMaster介绍3.1 applicatio…

Gurobi基础语法之 addConstr, addConstrs, addQConstr, addMQConstr

在新版本的 Gurobi 中&#xff0c;向 addConstr 这个方法中传入一个 TempConstr 对象&#xff0c;在模型中就会根据这个对象生成一个约束。更重要的是&#xff1a;TempConstr 对象可以传给所有addConstr系列方法&#xff0c;所以下面先介绍 TempConstr 对象 TempConstr TempC…

使用 Docker 部署 pSQL 服务器 的教程

如何使用 Edu 邮箱申请 Azure 订阅并开通免费 VPS 使用 Edu 邮箱不仅可以申请 Azure 的免费订阅来开通 VPS&#xff0c;还可以免费使用 Adobe 和 Notion 等软件&#xff0c;极大地提高学习和工作的效率。如果您还没有 Edu 邮箱&#xff0c;可以参考在线笔记s3.tebi.io/notes-i…

Python-基于PyQt5,wordcloud,pillow,numpy,os,sys等的智能词云生成器

前言&#xff1a;日常生活中&#xff0c;我们有时后就会遇见这样的情形&#xff1a;我们需要将给定的数据进行可视化处理&#xff0c;同时保证呈现比较良好的量化效果。这时候我们可能就会用到词云图。词云图&#xff08;Word cloud&#xff09;又称文字云&#xff0c;是一种文…

DNS缓存详解(DNS Cache Detailed Explanation)

DNS缓存详解 清空DNS缓存可以让网页访问更快捷。本文将从什么是DNS缓存、为什么清空DNS缓存、如何清空DNS缓存、清空DNS缓存存在的问题四个方面详细阐述DNS缓存清空的相关知识。 一、什么是DNS缓存 1、DNS缓存的定义&#xff1a; DNS缓存是域名系统服务在遇到DNS查询时自动…

Github 2025-02-01 开源项目月报 Top20

根据Github Trendings的统计,本月(2025-02-01统计)共有20个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目8TypeScript项目3Jupyter Notebook项目2Rust项目2HTML项目2C++项目1Ruby项目1JavaScript项目1Svelte项目1非开发语言项目1Go项目1Oll…

比较热门的嵌入式项目

嵌入式系统在现代科技中应用广泛&#xff0c;以下是一些当前比较热门的嵌入式项目方向及其应用场景&#xff1a; 1. 物联网&#xff08;IoT&#xff09; 智能家居&#xff1a;智能灯光、温控器、安防系统。环境监测&#xff1a;空气质量、温湿度、土壤湿度传感器。工业物联网&…

HarmonyOS NEXT:保存应用数据

用户首选项使用 用户首选项的特点 数据体积小、访问频率高、有加载速度要求的数据如用户偏好设置、用户字体大小、应用的配置参数。 用户搜选项&#xff08;Preferences&#xff09;提供了轻量级配置数据的持久化能力&#xff0c;支持订阅数据变化的通知能力。不支持分布式同…