首页 > 其他分享 >synchornized核心原理讲解

synchornized核心原理讲解

时间:2024-12-24 15:55:30浏览次数:6  
标签:Thread t2 synchornized t1 SyncUnit 线程 讲解 原理 public

前言

在此之前先有几个面试题,看大家能答对几题

1.1: 标准访问ab二个线程,是先打印t1还是t2 ???

public class SyncUnit {
    
    public synchronized void t1() {
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();

        Thread.sleep(100);

        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.2: t1方法暂停3秒钟,是先打印t1还是t2 ???

public class SyncUnit {

    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();

        Thread.sleep(100);

        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }

1.3: 新增一个普通方法hello(),是先打印t1还是hello ????

public class SyncUnit {

    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();

        Thread.sleep(100);

        new Thread(() -> {
           syncUnit.hello();
        }).start();
    }
}

1.4: 现在有二个SyncUnit对象,是先打印t1还是t2 ???

public class SyncUnit {

    public synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }

1.5: 二个静态同步方法,一个SuncUnit对象,是先打印t1还是t2 ????

public class SyncUnit {

    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.6: 二个静态同步方法,二个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {

    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }
}

1.7: 一个静态同步方法,普通同步方法,一个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {

    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit.t2();
        }).start();
    }
}

1.8 一个静态同步方法,普通同步方法,二个SyncUnit对象,是先打印t1还是t2

public class SyncUnit {

    public static synchronized void t1(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1");
    }

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

    public static void main(String[] args) throws Exception{
        SyncUnit syncUnit = new SyncUnit();
        SyncUnit syncUnit1 = new SyncUnit();
        new Thread(() -> {
            syncUnit.t1();
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
           syncUnit1.t2();
        }).start();
    }
}

synchronized用法

synchronized是java提供的一种解决多线程并发安全的一种内置锁,尽管在jdk1.5之前还被大家吐槽性能问题,但是在1.5之后对synchronized不断的优化,在单机程序中,当设计多线程并发问题时,我们完全可以使用synchronized解决

  • 同步实例方法

  • arduino复制代码

  • public synchronized void method() { //方法逻辑 }

  • 当synchronized修饰的是一个普通方法的时候,相当于对this对象加锁,一个实例是可以创建多个对象的,所以可以拥有多把锁,就比如下面这个例子,我们创建了二个对象,那就是二把不同的锁,所以在调用t1()的时候,t2()方法由于是不同的锁,所以会直接执行方法

public class SyncUnit {

        public synchronized void t1(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1");
        }

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

        public static void main(String[] args) throws Exception{
            SyncUnit syncUnit = new SyncUnit();
            SyncUnit syncUnit1 = new SyncUnit();
            new Thread(() -> {
                syncUnit.t1();
            }).start();
            Thread.sleep(100);
            new Thread(() -> {
               syncUnit1.t2();
            }).start();
        }
    }
  • 同步静态方法

  • arduino复制代码

  • public static synchronized void method() { //方法逻辑 }

  • 当synchronized修饰的是一个静态方法的时候,相当于对当前实例加锁,一个类只有一个实例,所以无论你创建多少个对象,都只有一把锁,比如下面这个例子,虽然创建了二个不同的对象,但是实际只有一把锁,所以是先打印t1(),然后在打印t2(),因为t2()要等待t1()把锁释放掉之后才能获取到锁


   public class SyncUnit {

            public static synchronized void t1(){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1");
            }

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

            public static void main(String[] args) throws Exception{
                SyncUnit syncUnit = new SyncUnit();
                SyncUnit syncUnit1 = new SyncUnit();
                new Thread(() -> {
                    syncUnit.t1();
                }).start();
                Thread.sleep(100);
                new Thread(() -> {
                   syncUnit1.t2();
                }).start();
            }
       }
  • 代码块

  • js复制代码

  • public Object object = new Object(); public void method() { synchronized(object) { //方法逻辑 } }

这时候的object是一个对象,就相当于在普通方法上添加synchronized,如果是不同的对象,那么就是不同的锁

      public void method() {
          synchronized(Test.class) {
             //方法逻辑
          }
       }

这时候就相当于在静态方法上添加synchronized,也就是对当前实例加锁,一个类只有一个实例

synchronized核心原理

synchornized是基于JVM中的Monitor锁实现的,Java1.5版本之前的synchornized锁性能较低,但是从1.6版本之后,对synchornized进行了大量的优化,引入了锁粗化,锁消除,偏向锁,轻量级锁,适应性自旋等技术来提升synchornized的性能

  • synchornized修饰的是方法

当synchornized修饰的是方法的时候,当前方法会比普通方法多一个ACC_SYNCHRONIZED的标识符

当JVM执行程序的时候,会判断这个方法是否有ACC_SYNCHRONIZED这个标识符,如果有,则当前线程优先获取Monitor对象,同一个时刻只能有一个线程获取到,在当前线程释放Monitor对象之前,其它线程无法获取到同一个Monitor对象,从而保证了同一时刻只能有一个线程进入到被synchornized修饰的方法

  • synchornized修饰的是代码块

当synchornized修饰的是代码块的时候,synchornized关键字会被编译成monitorentermonitorexit,使得同一时刻只能有一个线程进入到同步代码块中,但是这里为什么会有二个monitorexit,是因为程序正常退出的时候需要释放锁,在程序异常的时候也要释放锁,所以会对应二个

无论synchornized修饰的是方法还是代码块,底层都是通过JVM调用操作系统的Mutes锁实现的,当线程被阻塞时会被挂起,等待CPU重新调度,这会导致线程在操作系统的用户态和内核态之间切换,影响性能

Monitor锁原理

synchornized低成是基于Monitor锁来实现的,而Monitor锁是基于操作系统的Mutex锁实现的,Mutex锁是操作系统级别的重量级锁,所以性能较低

在Java中,创建的任何一个对象在JVM中都会关联一个Monitor对象,所以说任何一个对象都可以成为锁。

在HotSpot JVM中,Monitor是由ObjectMoitor实现的,在ObjectMonitor对象的数据结构中,有几个重要的属性

  • _WaitSet:是一个集合,当线程获到锁之后,但是还没有完成业务逻辑,也还没释放锁,这时候调用了Object类的wait()方法,这时候这个线程就会进入_WaitSet这个集合中等待被唤醒,也就是执行nitify()或者notifyAll()方法唤醒

  • _EntryList:是一个集合,当有多个线程来获取锁,这时候只有一个线程能成功拿到锁,剩下那些没有拿到锁的线程就会进入_EntryList集合中,等待下次抢锁

  • _Owner:当一个线程获取到锁之后,就会将该值设置成当前线程,释放锁之后,这个值就会重新被设置成null

  • _count:当一个线程获取到锁之后,_count的值就会+1,释放锁之后就会-1,只有当减到0之后,才算真正的释放掉锁了,其它线程才能来获取这把锁,synchornized可重入锁也是基于这个值来实现的

所以当多个线程同时访问被synchornized修饰的方法或者代码块时候,synchornized加锁和释放锁的底层实现流程大致为:

  • 1:进入_EntryList集合,当某个线程获取到锁之后,这个线程就会进入_Owner区域,就会将Monitor对象的_owner变量复制为当前线程。并把_count值+1

  • 2:当线程调用wait()方法时,当前线程会释放掉持有的Monitor对象,并把_owner赋值成null,_count的值-1,同时这个线程就会进入_WaitSet集合等到被唤醒

  • 3:如果获取到锁的线程执行完毕,也会释放Monitor锁。,_owner被置为null,_count被置为0

偏向锁

虽然在程序的方法中或代码块中添加了synchornized,但是在大部分的情况下,不会存在多线程竞争这种情况,并且会出现同一个线程多次获取同一把锁的现象,为了提升这种情况下程序的性能,引入了偏向锁

轻量级锁

当多线程竞争锁不激烈时,可以通过CAS机制竞争锁,这就是轻量级锁,引入轻量级锁的目的是在多线程竞争锁不激烈时,避免由于使用操作系统层面的Mutex重量级锁导致性能低下

重量级锁

重量级锁主要是基于操作系统的Mutex锁实现,重量级锁的执行效率较低,处于重量级锁时被阻塞的线程不会消耗CPU资源

锁升级过程

多个线程在争抢synchornized锁时,在某些情况下,会由无锁状态一步步升级为最终的重量级锁,整个升级过程大致包括如下几个步骤

  • 1:线程在竞争synchornized时,JVM首先会检查锁对象的Mark Word中偏向锁的标记位是否为1,锁标记位是否为01,如果二个条件都满足,则当前锁处于偏向锁状态

  • 2:争抢synchornized锁线程检查锁对象的Mark Work中存储的线程ID是否是自己的,如果是自己的线程ID,则表示处于偏向锁状态,当前线程可以直接进入方法或者代码块

  • 3:如果锁对象的Mark Word的线程ID不是自己的线程ID,那么就会通过CAS方式来竞争锁资源,如果获取到锁资源了,就将Mark Word中存储的线程ID修改成自己的线程ID,将偏向锁的标记设置成1,锁标记位置设置成01,当前锁处于偏向锁状态

  • 4:如果当前线程通过CAS没有获取到锁资源,则说明有其它线程也在争抢资源,此时会撤销偏向锁,升级为轻量级锁,并将Mark Word的锁标记为都清空

  • 5:当前线程与其它线程还是会通过CAS方式来竞争资源,如果某个线程成功获取到资源,就会将锁对象的Mark Word中的锁标志位设置成00,此时进入轻量级锁状态

  • 6:竞争失败的线程还是会通过CAS方式来获取锁,但是当CAS达到一定的次数以后,就会升级为重量级锁了

标签:Thread,t2,synchornized,t1,SyncUnit,线程,讲解,原理,public
From: https://blog.csdn.net/qq_66627105/article/details/144509631

相关文章

  • 反向 Debug 了解一下?揭秘 Java DEBUG 的基本原理
    作者:京东保险蒋信Debug的时候,都遇到过手速太快,直接跳过了自己想调试的方法、代码的时候吧……一旦跳过,可能就得重新执行一遍,准备数据、重新启动可能几分钟就过去了。好在IDE们都很强大,还给你后悔的机会,可以直接删除某个StackFrame,直接返回到之前的状态,确切的说是返回到之......
  • C语言-详细讲解-动态数组统计成绩
    1.题目要求用动态数组编程输入任意m个班学生(每班n个学生)的某门课的成绩,计算最高分,并指出具有该最高分成绩的学生是第几个班的第几个学生。其中,m和n的值由用户从键盘任意输入(不限定m和n的上限值)。输入提示信息:"Inputarraysizem,n:""Input%d*%darray:\n"输入m,n的格式......
  • .NET Core 类型系统(Types System)底层原理浅谈
    C#类型系统C#是一种强类型语言。每个变量和常量都有一个类型,每个求值的表达式也是如此。每个方法声明都为每个输入参数和返回值指定名称、类型和种类(值、引用或输出)。.NET类库定义了内置数值类型和表示各种构造的复杂类型。其中包括文件系统、网络连接、对象的集合和数组以......
  • Lock锁并发原理
    packagecom.wb.demo.util;importcom.graphbuilder.struc.LinkedList;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclass......
  • 27. java反射的作用于原理
    1、定义:反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。2、哪里会用......
  • 【游戏设计原理】25 - 社会关系
    在游戏中融入并激活玩家的社会关系网络,能有效提升玩家的兴趣、参与度与满意度,并最终增强游戏黏性。一、原理分析与总结社交性在游戏中的重要性许多游戏都依赖玩家之间的互动(如棋盘、卡牌以及线上游戏),即便有单机游戏存在,更多更主流的玩法通常强调多人社交性。从“大杀四......
  • 薅羊毛的原理你知道吗?
    “薅羊毛”在互联网语境中,通常指的是利用一些优惠活动、漏洞或技巧,以低成本甚至零成本获取利益的行为。在前端开发的领域,虽然“薅羊毛”并不是一个专业术语,但我们可以从技术和用户行为的角度来探讨其可能的原理。技术原理:自动化脚本:使用JavaScript等前端脚本语言,编写自动化脚本......
  • 大语言模型驱动的Agent:定义、工作原理与应用
    文章目录引言什么是大语言模型?Agent的概念LLMAgent的工作原理Dify平台上的AgentLLMAgent的应用场景挑战与展望结论引言随着人工智能(AI)技术的发展,特别是自然语言处理(NLP)领域的进步,大语言模型(LLM,LargeLanguageModels)已经成为AI领域的一颗璀璨明星。这些模......
  • CountDownLatch底层原理、源码解析
    CountDownLatch通过AQS实现了基于计数器的同步机制。多个线程可以在计数值未达到0时进入等待状态,而其他线程可以通过调用countDown()减少计数值。当计数值减至0时,所有等待的线程会被唤醒并继续执行。下面只保留关键代码,解析见注释。CountDownLatch:publicclassCountDown......
  • 《计算机组成及汇编语言原理》阅读笔记:p48-p81
    《计算机组成及汇编语言原理》学习第4天,p48-p81总结,总计34页。一、技术总结1.CISCvsRISCp49,complexinstructionsetcomputingForexample,acomplexinstructionsetcomputing(CISC)chipmaybeabletomovealargeblockofdata,perhapsastringconsist......