synchronized

Aveiro 2016年02月01日 ⋅ 591 阅读
  • synchronized修饰非静态方法

以当前对象作为锁

  • synchronized修饰静态方法

以当前类的class作为锁

  • synchronized修饰代码块

以synchronized后填写的作为锁

 

在Hotspot虚拟机中对象在内存中的布局

  • 对象头

标记字段:哈希码+GC分代年龄+锁状态标志

类型指针:通过指针来确定对象是哪个类的实例

  • 实例数据

程序中定义的各种类型数据

  • 对齐填充

Hotspot虚拟机要求对象大小必须是8字节的整数倍,对象头是8字节的1倍或2倍,当实例数据不是8字节的整数倍时,就需要对齐填充来补全

 

锁实现

synchronized对象锁,其指针指向的是monitor对象的起始地址,java中每一个对象都有一个由C++实现的monitor,monitor可以与对象一起创建和销毁、或者当线程尝试获取对象锁时创建。

ObjectMonitor.hpp

ObjectMonitor(){
  //省略部分代码
  _count=0;
  _owner=NULL;// 指向持有该ObjectMonitor对象的线程
  _WaitSet=NULL;// 等待被唤醒的线程集合
  _WaitSetLock=0;
  _EntryList=NULL;// 当多个线程竞争时,会先存放到_EntryList中
}

_owner指向持有该ObjectMonitor对象的线程,当多个线程竞争时,会先存放到_EntryList中进行阻塞等待,当线程获取到对象的monitor后,_owner指向该线程,同时_count+1,如果线程调用的wait()方法,会释放当前持有的monitor、此时_owner会置为NULL、并且_count-1、该线程还会放入_WaitSet中、等待被唤醒,如果线程顺利执行完成,会释放持有的monitor,_owner会置为NULL、并且_count-1

 

代码块加解锁实现

L.java

public class L {
    public void m2() {
        synchronized (this) {
            System.out.println("222");
        }
    }
}

编译生成.class

javac L.java

解析字节码

javap -v L.class

 

......
  public void m2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         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 222
         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:
......

从字节码可以看出底层synchronized对象锁底层使用monitorenter、monitorexit实现线程同步

  • 执行monitorenter指令,线程将尝试获取monitor对象,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,那么就把锁的计数器_count加1
  • 执行monitorexit指令,将count计数器减一,当计数器为0时,其他线程将有机会持有

字节码中看出有两个monitorexit,其中一个是异常结束时执行。

 

 

方法加解锁实现

Li.java

public class Li {
    public synchronized void m1() {
        System.out.println("222");
    }
}

编译生成.class

javac Li.java

解析字节码

......
 public synchronized void m1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String 222
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 10: 8
}

从字节码可以看出没有monitorenter和monitorexit,取而代之的是ACC_SYNCHRONIZED标识,JVM通过ACC_SYNCHRONIZED标识,就可以知道这是一个需要同步的方法,进而执行上述同步的过程,也就是_count加1这些过程。

 


全部评论: 0

    我有话说: