您当前的位置: 首页 > 

Dongguo丶

暂无认证

  • 2浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

synchronized

Dongguo丶 发布时间:2021-09-16 08:58:11 ,浏览量:2

synchronized

synchronized是Java中的关键字,是一种同步锁。它可以保证方法或者代码块在运行时,同一时刻只有一个线程可以访问,同时它还可以保证共享变量的内存可见性,和操作的原子性。

它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。

  1. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  2. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
synchronized的三种应用方式

Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

普通同步方法,锁是当前实例对象

静态同步方法,锁是当前类的 class 对象

同步代码块,锁是Synchonized 括号里面的对象

底层原理

Java 中每一个对象都可以作为锁,是因为每个对象都有一个监视器锁(monitor)。

当监视器锁的已经有持有者了,

表示锁已经被其他线程获取

当监视器锁的持有者为null时,

表示可以获取锁

3当线程获取锁失败后,则该线程进入阻塞状态,直到监视器锁的持有者为null,再重新尝试获取监视器锁。

总结

synchronized 是重量级锁,在 JDK1.6 中进行优化,如锁升级、锁消除、锁粗化等技术来减少锁操作的开销。1.8之后可以等同ReentrantLock的使用

管程

管程 (英语:Monitor,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。

Monitor是操作系统层面的,在Java层面是看不到的

这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

image-20210908185744002

Monitor 结构如下

image-20210911160648804

刚开始 Monitor 中 Owner 为 null 当 Thread-2 执行 synchronized(obj) 获取锁,就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner,即 Thread-2是obj对象对应的Monitor 的持有者 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),发现已经有其他线程关联了obj对象的Monitor 的Owner,就会进入EntryList(同步队列) BLOCKED阻塞状态 Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足(调用了wait()方法)进入 WAITING 状态的线程,

注意: synchronized 必须是进入同一个对象的 monitor 才有上述的效果(每个对象都有一个monitor ) 不加 synchronized 的对象不会关联监视器,不遵从以上规则

售票案例

实现3个售票员卖出100张票的案例

分析:线程 操作 资源类

第一 创建资源类

第二 创建多线程调用资源类的方法

票就是资源类,它的属性就是有多少张,操作方法就是卖出

3个售票员就是创建三个线程

不进行同步方案
package com.dongguo.concurrent.synchronize;

/**
 * @author Dongguo
 * @date 2021/9/3 0003-10:14
 * @description: 实现3个售票员卖出100张票的案例
 * 没有使用同步锁,存在超卖问题
 */
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            //循环100次保证能够卖光票
            for (int i = 0; i  {
            for (int i = 0; i  {
            for (int i = 0; i  0) {
                //为了体现超卖现象,线程休眠100ms
                Thread.sleep(100);
                count--;
                System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20210903104708843

number被三个线程共享,同时调用saleTicket方法

发现问题:票重复卖出, 顺序混乱,卖出超过100张票

使用synchronized解决并发导致超卖的问题 同步方法
package com.dongguo.concurrent.synchronize;

/**
 * @author Dongguo
 * @date 2021/9/3 0003-10:14
 * @description: 实现3个售票员卖出100张票的案例
 * 使用同步方法,不存在超卖问题
 */
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            //循环100次保证能够卖光票
            for (int i = 0; i  {
            for (int i = 0; i  {
            for (int i = 0; i  0) {
                //为了体现超卖现象,线程休眠100ms
                Thread.sleep(100);
                count--;
                System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20210903105928152

在循环的方法上添加 synchronized ,

虽然线程排队打印,运行没有发生超卖问题,但是运行效率比较低

同步代码块
package com.dongguo.concurrent.synchronize;

/**
 * @author Dongguo
 * @date 2021/9/3 0003-10:14
 * @description: 实现3个售票员卖出100张票的案例
 * 使用同步代码块,不存在超卖问题
 */
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            //循环100次保证能够卖光票
            for (int i = 0; i  {
            for (int i = 0; i  {
            for (int i = 0; i  0) {
                    //为了体现超卖现象,线程休眠100ms
                    Thread.sleep(100);
                    count--;
                    System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

速度些许提升也没有发生乱序重复的现象

image-20210903100145892

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有; 2)线程执行发生异常,此时JVM会让线程自动释放锁。 那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

使用Synchronized时需要注意什么?

1.Synchronized使用时需要注意的地方锁对象不能为空。

锁对象的信息是保留在对象头中的,如果对象为空,则锁的信息也就不存在了。

2.作用域不宜过大

synchronized代码块的代码量不宜过多,如果把过多的代码放在其中,程序的运行会变为串行,速度会下降。各个线程并行可以提高效率,我们应该仅把那些影响线程安全的代码,放入synchronized代码块中,串行执行;不需要考虑线程安全的代码,并行执行,达到效率最高。

3.避免死锁

避免让线程对锁持有并等待的情况出现

阿里开发手册

【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能·锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。

从字节码角度分析synchronized实现

查看字节码命令javap

javap命令反汇编一个或多个类文件。输出取决于所使用的选项。当不使用任何选项时,javap命令将打印包、受保护字段和公共字段以及传递给它的类的方法。javap命令将其输出打印到stdout。

javap -c xxx.class

-c

为类中的每个方法打印反汇编代码,例如,组成Java字节码的指令,即 对代码进行反汇编

javap -v  xxx.class

-v

即**-verbose**

输出附加信息 比如打印方法的参数,行号、本地变量表,反汇编等详细信息

同步代码块
package com.dongguo.synclock;

/**
 * @author Dongguo
 * @date 2021/9/5 0005-21:23
 * @description: 字节码角度分析synchronized
 */
public class LockByteCodeDemo {

    Object object = new Object();

    //同步代码块
    public void m1() {
        synchronized (object) {
            System.out.println("同步代码块");
        }
    }

    public static void main(String[] args) {

    }
}

经过编译

IDEA 中 Build ->Build Project

查看target生成的LockByteCodeDemo.class文件右键 OPen In -> Terminal

输入命令

javap -v LockByteCodeDemo.class

输出信息

public void m1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;
         4: dup
         5: astore_1                        //将lock引用复制一份存到slot1中
         6: monitorenter					//将lock对象的markword指针指向Monitor  即加锁
         7: getstatic     #4                  // Field    java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #5                  // String 同步代码块
        12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: aload_1							//从slot1中获得临时的lock引用					
        16: monitorexit								//将lock对象的makrword重置,唤醒等待队列EntryList的线程
        17: goto          25    //调到25行
        20: astore_2     //e  将异常e存到slot2中
        21: aload_1				//从slot1中获得临时的lock引用
        22: monitorexit         //解锁
        23: aload_2				//从slot2中获得异常e
        24: athrow       //抛出异常e
        25: return
      Exception table:
         from    to  target type
             7    17    20   any    //监控7-17行 如果出现异常指向20行
            20    23    20   any
      LineNumberTable:
        line 14: 0
        line 15: 7
        line 16: 15
        line 17: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  this   Lcom/dongguo/synclock/LockByteCodeDemo;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class com/dongguo/synclock/LockByteCodeDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

synchronized同步代码块实现使用的是monitorenter和monitorexit指令

当一个线程进入同步代码块时,它使用monitorenter指令请求进入。如果当前对象的监视器计数器为0,则它会被准许进入,若为1,则判断持有当前监视器的线程是否为自己,如果是,则进入,否则进行等待,直到对象的监视器计数器为0,才会被允许进入同步块。当线程退出同步块时,需要使用monitorexit声明退出。

在Java虚拟机中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态。指令monitorenter和monitorexit在执行时,都需要在操作数栈顶压入对象,之后monitorenter和monitorexit的锁定和释放都是针对这个对象的监视器进行的。下图展示了监视器如何保护临界区代码不同时被多个线程访问,只有当线程4离开临界区后,线程1、2、3才有可能进入。

image-20210905231658477

编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指令

monitorenter:monitorexit 为 1:2 why???

因为第一个monitorexit正常完成解锁

第二个monitorexit保证出现Exception或者Error也能解锁,

确保一定能够解锁 不会发生死锁

那么一定是一个enter两个exit吗?

m1方法里面自己添加一个异常试试

package com.dongguo.synclock;

/**
 * @author Dongguo
 * @date 2021/9/5 0005-21:23
 * @description: 字节码角度分析synchronized
 */
public class LockByteCodeDemo {

    Object object = new Object();

    //同步代码块
    public void m1() {
        synchronized (object) {
            System.out.println("同步代码块");
            //抛出异常
            throw new RuntimeException("出现异常");
        }
    }

    public static void main(String[] args) {

    }
}

字节码

 public void m1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #5                  // String 同步代码块
        12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: new           #7                  // class java/lang/RuntimeException
        18: dup
        19: ldc           #8                  // String 出现异常
        21: invokespecial #9                  // Method java/lang/RuntimeException."":(Ljava/lang/String;)V
        24: athrow
        25: astore_2
        26: aload_1
        27: monitorexit
        28: aload_2
        29: athrow
      Exception table:
         from    to  target type
             7    28    25   any
      LineNumberTable:
        line 14: 0
        line 15: 7
        line 17: 15
        line 18: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      30     0  this   Lcom/dongguo/synclock/LockByteCodeDemo;
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class com/dongguo/synclock/LockByteCodeDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]

只有一个monitorenter和一个monitorexit

athrow了两次

确保一定能够抛出异常

普通同步方法
package com.dongguo.synclock;

/**
 * @author Dongguo
 * @date 2021/9/5 0005-21:23
 * @description: 字节码角度分析synchronized
 */
public class LockByteCodeDemo {

    Object object = new Object();

    //同步代码块
//    public void m1() {
//        synchronized (object) {
//            System.out.println("同步代码块");
//            //抛出异常
//            throw new RuntimeException("出现异常");
//        }
//    }
    //普通同步方法
    public synchronized void m2() {
        System.out.println("普通同步方法");
    }

    public static void main(String[] args) {

    }
}

字节码

  public synchronized void m2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String 普通同步方法
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 22: 0
        line 23: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/dongguo/synclock/LockByteCodeDemo;

普通同步方法没有monitorenter和monitorexit指令

但是flags有一个ACC_SYNCHRONIZED标志

方法级的同步:是隐式的, 即无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC-SYNCHRONIZED访问标志得知一个方法是否声明为同步方法;当调用方法时,调用指令将会检查方法的ACC-SYNCHRONIZED访问标志是否设置。如果设置了,执行线程将先持有同步锁,然后执行方法。最后在方法完成(无论是正常完成还是非正常完成)时释放同步锁。在方法执行期间,执行线程持有了同步锁,其他任何线程都无法再获得同一个锁。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛到同步方法之外时自动释放。

没有使用monitorenter和monitorexit进行同步区控制。这是因为,对于同步方法而言,当虚拟机通过方法的访问标示符判断是一个同步方法时,会自动在方法调用前进行加锁,当同步方法执行完毕后,不管方法是正常结束还是有异常抛出,均会由虚拟机释放这个锁。因此,对于同步方法而言, monitorenter和monitorexit指令是隐式存在的,并未直接出现在字节码中。

从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对 象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

静态同步方法
package com.dongguo.synclock;

/**
 * @author Dongguo
 * @date 2021/9/5 0005-21:23
 * @description: 字节码角度分析synchronized
 */
public class LockByteCodeDemo {

    Object object = new Object();

    //同步代码块
//    public void m1() {
//        synchronized (object) {
//            System.out.println("同步代码块");
//            //抛出异常
//            throw new RuntimeException("出现异常");
//        }
//    }
    //普通同步方法
//    public synchronized void m2() {
//        System.out.println("普通同步方法");
//    }
    //静态同步方法
    public static synchronized void m3() {
        System.out.println("静态同步方法");
    }

    public static void main(String[] args) {

    }
}

字节码

  public static synchronized void m3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String 静态同步方法
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 26: 0
        line 27: 8

此时的flags 多了ACC_STATIC,ACC_SYNCHRONIZED标志

ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否是静态同步方法

为什么每一个对象都可以成为一个锁?

Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

image-20210908210455921

monitor

Java 中每一个对象都可以作为锁,是因为每个对象都有一个监视器锁(monitor)。对象都继承Object类

在HotSpot虚拟机中,monitor采用ObjectMonitor实现

image-20210908210420212

objectMonitor.hpp

  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
  

ObjectMonitor中有几个关键属性 _owner 指向持有ObjectMonitor对象的线程 _WaitSet 存放处于wait状态的线程队列 _EntryList 存放处于等待锁block状态的线程队列 _recursions 锁的重入次数 _count 用来记录该线程获取锁的次数

Monitor与java对象以及线程是如何关联 ? 1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址 2.Monitor的Owner字段会存放拥有相关联对象锁的线程id

Mutex Lock

Monitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。

mutex.hpp

private:
   int  TrySpin (Thread * Self) ;
   int  TryLock () ;
   int  TryFast () ;
   int  AcquireOrPush (ParkEvent * ev) ;
   void IUnlock (bool RelaxAssert) ;
   void ILock (Thread * Self) ;
   int  IWait (Thread * Self, jlong timo);
   int  ILocked () ;

 protected:
   static void ClearMonitor (Monitor * m, const char* name = NULL) ;
   Monitor() ;

 public:
  Monitor(int rank, const char *name, bool allow_vm_block=false);
  ~Monitor();

  // Wait until monitor is notified (or times out).
  // Defaults are to make safepoint checks, wait time is forever (i.e.,
  // zero), and not a suspend-equivalent condition. Returns true if wait
  // times out; otherwise returns false.
  bool wait(bool no_safepoint_check = !_no_safepoint_check_flag,
            long timeout = 0,
            bool as_suspend_equivalent = !_as_suspend_equivalent_flag);
  bool notify();
  bool notify_all();


  void lock(); // prints out warning if VM thread blocks
  void lock(Thread *thread); // overloaded with current thread
  void unlock();
  bool is_locked() const                     { return _owner != NULL; }

  bool try_lock(); // Like lock(), but unblocking. It returns false instead

  // Lock without safepoint check. Should ONLY be used by safepoint code and other code
  // that is guaranteed not to block while running inside the VM.
  void lock_without_safepoint_check();
  void lock_without_safepoint_check (Thread * Self) ;

  // Current owner - not not MT-safe. Can only be used to guarantee that
  // the current running thread owns the lock
  Thread* owner() const         { return _owner; }
  bool owned_by_self() const;

  // Support for JVM_RawMonitorEnter & JVM_RawMonitorExit. These can be called by
  // non-Java thread. (We should really have a RawMonitor abstraction)
  void jvm_raw_lock();
  void jvm_raw_unlock();
  const char *name() const                  { return _name; }

  void print_on_error(outputStream* st) const;

  #ifndef PRODUCT
    void print_on(outputStream* st) const;
    void print() const                      { print_on(tty); }
    debug_only(int    rank() const          { return _rank; })
    bool   allow_vm_block()                 { return _allow_vm_block; }

    debug_only(Monitor *next()  const         { return _next; })
    debug_only(void   set_next(Monitor *next) { _next = next; })
  #endif

  void set_owner(Thread* owner) {
  #ifndef PRODUCT
    set_owner_implementation(owner);
    debug_only(void verify_Monitor(Thread* thr));
  #else
    _owner = owner;
  #endif
  }

};

mutex.cpp

void Monitor::lock (Thread * Self) {
#ifdef CHECK_UNHANDLED_OOPS
  // Clear unhandled oops so we get a crash right away.  Only clear for non-vm
  // or GC threads.
  if (Self->is_Java_thread()) {
    Self->clear_unhandled_oops();
  }
#endif // CHECK_UNHANDLED_OOPS

  debug_only(check_prelock_state(Self));
  assert (_owner != Self              , "invariant") ;
  assert (_OnDeck != Self->_MutexEvent, "invariant") ;

  if (TryFast()) {
 Exeunt:
    assert (ILocked(), "invariant") ;
    assert (owner() == NULL, "invariant");
    set_owner (Self);
    return ;
  }

  // The lock is contended ...

  bool can_sneak = Self->is_VM_thread() && SafepointSynchronize::is_at_safepoint();
  if (can_sneak && _owner == NULL) {
    // a java thread has locked the lock but has not entered the
    // critical region -- let's just pretend we've locked the lock
    // and go on.  we note this with _snuck so we can also
    // pretend to unlock when the time comes.
    _snuck = true;
    goto Exeunt ;
  }

  // Try a brief spin to avoid passing thru thread state transition ...
  if (TrySpin (Self)) goto Exeunt ;

  check_block_state(Self);
  if (Self->is_Java_thread()) {
    // Horribile dictu - we suffer through a state transition
    assert(rank() > Mutex::special, "Potential deadlock with special or lesser rank mutex");
    ThreadBlockInVM tbivm ((JavaThread *) Self) ;
    ILock (Self) ;
  } else {
    // Mirabile dictu
    ILock (Self) ;
  }
  goto Exeunt ;
}

void Monitor::lock() {
  this->lock(Thread::current());
}

那用户态和内核态又是啥呢?

Linux系统的体系结构大家大学应该都接触过,分为用户空间(应用程序的活动空间)和内核。

我们所有的程序都在用户空间运行,进入用户运行状态也就是(用户态),但是很多操作可能涉及内核运行,比如I/O,我们就会进入内核运行状态(内核态)。

img

这个过程是很复杂的,也涉及很多值的传递,我简单概括下流程:

  1. 用户态把一些数据放到寄存器,或者创建对应的堆栈,表明需要操作系统提供的服务。
  2. 用户态执行系统调用(系统调用是操作系统的最小功能单位)。
  3. CPU切换到内核态,跳到对应的内存指定的位置执行指令。
  4. 系统调用处理器去读取我们先前放到内存的数据参数,执行程序的请求。
  5. 调用完成,操作系统重置CPU为用户态返回结果,并执行下个指令。

所以大家一直说,1.6之前是重量级锁,没错,但是他重量的本质,是ObjectMonitor调用的过程,以及Linux内核的复杂运行机制决定的,大量的系统资源消耗,所以效率才低。

参考

死磕synchronized底层实现

关注
打赏
1638062488
查看更多评论
立即登录/注册

微信扫码登录

0.0369s