JVM从虚拟机字节码中看关键字synchronized实现

        Java语言中的synchronized关键字表示同步,可以作为函数的修饰符或者函数内的语句,当一个线程访问实例对象中被synchronized修饰的方法或者synchronized修饰的同步块时,线程获取该实例的对象级别的锁,此时其他线程如果要访问相同方法的时候就需要阻塞等待,直到前面的线程从同步块或者同步方法中退出并释放锁。

      Java虚拟机可以支持指令序列的同步,同步是通过监视器(Monitors)来实现的, 监视器主要功能是监控一段代码,确保在同一时间只有一个线程在执行。编写一个测试类一个方法中使用同步块synchronized ,第二个方法使用synchronized 修饰,测试代码如下

public class User {
    private String username;
    private String password;
    private String ip = new String();

    public void setUser1(String usrename, String password) {

        synchronized (ip) {
            this.username = usrename;
            this.password = password;
            System.out.println(Thread.currentThread().getName());
        }
    }

    public synchronized void setUser2(String usrename, String password) {
        this.username = usrename;
        this.password = password;
        System.out.println(Thread.currentThread().getName());
    }

}

 

#将java文件编译成class文件,并查看字节码

#将java文件编译成class文件
D:\>javac User.java

#获取class文件的字节码
D:\>javap -c User

先查看上部分 setUser1 方法的字节码信息

Compiled from "User.java"
public class com.programing.access.User extends java.lang.Object{
public com.programing.access.User();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new     #2; //class java/lang/String
   8:   dup
   9:   invokespecial   #3; //Method java/lang/String."<init>":()V
   12:  putfield        #4; //Field ip:Ljava/lang/String;
   15:  return

public void setUser1(java.lang.String, java.lang.String);
  Code:
   0:   aload_0     将对象放入栈
   1:   getfield        #4; //Field ip:Ljava/lang/String;
   4:   dup   复制栈顶元素 其实就是IP对象
   5:   astore_3
   6:   monitorenter   已栈顶元素作为锁,开始同步
   7:   aload_0
   8:   aload_1
   9:   putfield        #5; //Field username:Ljava/lang/String;
   12:  aload_0
   13:  aload_2
   14:  putfield        #6; //Field password:Ljava/lang/String;
   17:  getstatic       #7; //Field java/lang/System.out:Ljava/io/PrintStream;
   20:  invokestatic    #8; //Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
   23:  invokevirtual   #9; //Method java/lang/Thread.getName:()Ljava/lang/String;
   26:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   29:  aload_3
   30:  monitorexit   退出同步
   31:  goto    41   直接跳转到41步骤
   34:  astore  4
   36:  aload_3
   37:  monitorexit   这个退出同步是异常时候退出同步
   38:  aload   4
   40:  athrow
   41:  return   方法完成退出
  Exception table:
   from   to  target type
     7    31    34   any
    34    38    34   any

        从反编译的同步代码块可以看到同步块是由monitorenter指令进入,然后monitorexit释放锁,在执行monitorenter之前需要尝试获取锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,那么就把锁的计数器加1。当执行monitorexit指令时,锁的计数器也会减1。当获取锁失败时会被阻塞,一直等待锁被释放。

       

      上面的子界面中有2个monitorexit 实现退出同步,30行的退出同步是无异常执行完成,然后GOTO调转到41实现return操作。37行的monitorexit 退出同步是处理发生异常时候是实现退出同步锁 ,因为编译器会自动产生一个异常处理器,这个异常处理 器声明可处理所有的异常,它的目的就是用来执行monitorexit指令

     

查看官方文档:Chapter 6. The Java Virtual Machine Instruction Set 对monitorenter指令和monitorexit 指令有详细描述

monitorenter(用于实现进入监视器)

The objectref must be of type reference.

监视器的对象必须是引用对象

If objectref is null, monitorenter throws a NullPointerException.

监视器的对象如果为空,则会抛出空指针异常

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

每一个监视器都需要和一个所有者对象关联,当且仅当监视器有了所有者对象的时候,监视器被锁定,执行monitorenter的线程试图获得监视器的所有权的方式如下:

  • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

如果与objectref关联的监视器的条目计数为零,则线程进入监视器并将其条目计数设置为1。线程就是之后一段时间内的监视器的所有者

  • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

如果线程已经拥有与objectref关联的监视器,它将重新进入监视器,增加其条目计数

  • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

如果另一个线程已经拥有与objectref相关联的监视器,该线程将阻塞,直到监视器的条目计数为零,然后再次尝试获得所有权。

monitorexit(用于实现退出监视器)

The objectref must be of type reference.

objectref必须是引用类型

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

执行monitorexit的线程必须是与objectref引用的实例关联的监视器的所有者

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

线程递减与objectref关联的监视器的条目计数时,如果结果是条目计数的值为零,则线程退出监视器,不再是它的所有者。正被阻塞进入监视器的其他线程可以尝试进入监视器

官方文档中有句话The monitorenter and monitorexit instructions are not used in the implementation ofsynchronized methods, although they can be used to provide equivalent locking semantics.意思是说monitorenter和monitorexit指令不用于同步方法的实现,尽管它们可以用来提供等效的锁定语义,查看setUser2方法的字节码,可以看到没有使用monitorenter指令和monitorexit 指令

public synchronized void setUser2(java.lang.String, java.lang.String);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #5; //Field username:Ljava/lang/String;
   5:   aload_0
   6:   aload_2
   7:   putfield        #6; //Field password:Ljava/lang/String;
   10:  getstatic       #7; //Field java/lang/System.out:Ljava/io/PrintStream;
   13:  invokestatic    #8; //Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
   16:  invokevirtual   #9; //Method java/lang/Thread.getName:()Ljava/lang/String;
   19:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   22:  return

}

对于方法级别的同步官网的描述 Chapter 2. The Structure of the Java Virtual Machine

Synchronization

Method-level synchronization is performed implicitly, as part of method invocation and return (§2.11.8). A synchronized method is distinguished in the run-time constant pool's method_info structure (§4.6) by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions.

方法级别的同步是隐式执行的,同步是作为方法调用和返回的一部分,运行同步方法是通过运行常量池中的method_info结构,通过ACC_SYNCHRONIZED标识来区分同步是否是同步方法,方法调用的指令检查这个标记,

When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly

当调用设置了ACC_SYNCHRONIZED的方法时候,执行线程就会进入监视器,然后调用标记的方法,无论方法调用是否正常完成或者异常,线程最后都会退出监视器

During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

当执行线程拥有监视器的这段时间内,其他现场不能进入监视器, 如果调用synchronized方法发生异常而这个方法不捕获处理异常的时候,方法的监视器将会在方法抛出异常前自动退出

  

上一篇:JVM虚拟机中的.class文件icon-default.png?t=L892https://blog.csdn.net/Beijing_L/article/details/120272564

Guess you like

Origin blog.csdn.net/Beijing_L/article/details/120293434