synchronized

sychronized代码块如何保证加锁解锁的成对出现?

对如下这样一个简单的代码用javap查看的bytecode信息

public class public class Demo {
		public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("ok");
        }
    }
}

javac -g Demo.java

javap -v Demo

public static void mainpublic static void main(java.lang.String[]);
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
     stack=2, locals=4, args_size=1
        -------- 0 ~ 7 为 Object lock = new Object()对 应的bytecode -----
     	 //new在堆中产生新的对象的同时,将对象的一个引用放入操作数栈
        0: new           #2                  // new Object
        //dup复制一份对象的引用,是为了用2次
        3: dup
        //执行invokespecial指令,消耗栈顶的一个对象的引用,调用构造方法
        4: invokespecial #1                  
        7: astore_1   //第二个lock引用赋给局部变量表中1号slot的name为lock的局部变量
                 
        8: aload_1    //将对象引用加载到操作数栈
        9: dup				 //再复制一份,分别给monitorenter,monitorexit使用,分别为加锁和解锁
       10: astore_2   //将刚才新产生的对象引用 存储到2号slot(没有名字的slot)
       11: monitorenter   //还剩的一个对象引用被monitorenter消耗掉,这就是加锁操作
       -------- 12 ~ 21 为sychronized代码块内部-----          
       12: getstatic     #3                  // < - System.out 
       15: ldc           #4                  // < = "ok"
       17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       20: aload_2    //将刚才存到slot_2的对象引用加载到操作数栈给解锁指令用
       21: monitorexit
     	22: goto          30
       //怎样保证一定会解锁?利用异常表
       25: astore_3   // 将异常对象的引用存到slot_3
       26: aload_2    // < - slot_2(lock引用)
       27: monitorexit  // monitorexit(lock引用),确保对同一个对象解锁
       28: aload_3    //刚才的异常对象引用加载进来,进行抛出
       29: athrow
       30: return
     Exception table:
        from    to  target type
           12    22    25   any
           25    28    25   any
     LocalVariableTable:
     Start  Length  Slot  Name   Signature
         0      31     0  args   [Ljava/lang/String;
         8      23     1  lock   Ljava/lang/Object;

总结:

1.sychronized用于对象上时,会产生2份对象的引用,分别给加锁和解锁指令用

2.sychronized用于对象上时,利用异常表,不管sychronized中的代码是否正常执行,都将会对同一个对象解锁

重量级锁

轻量级锁

提升同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的。

轻量级锁使用CAS操作避免了使用重量级锁(互斥量)的开销。

加锁过程

  1. 建立锁记录,存储Mark Word

    如果此同步对象没有被锁定(锁标志位为01),虚拟机首先在当前线程栈帧中建立一个锁记录的空间,用于存储锁对象当前的Mark Word的拷贝

  2. 使用CAS将Mark Word指向锁记录

    1. 操作成功,进入轻量级锁定状态,锁标志改为00
    2. CAS操作失败,检查对象的Mark Word是否指向当前线程的栈帧
      1. 是,进入同步块继续执行(锁重入)
      2. 否,说明这个锁对象被其他线程抢占了,锁膨胀为重量级锁(锁标志位为10),自己进入阻塞状态。

锁对象:门。

重量级锁:防盗锁。

轻量级锁:书包(不同人书包是一样的,里面的书本不一样)

偏向锁:写名字

偏向锁

1.是JDK1.6种引入的一项锁优化

2.它的目的是消除数据在有同步无竞争情况下的同步原语,进一步提高程序的运行性能。

3.如果说轻量级所是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。

开始是偏向锁,有其他线程来时,竞争发生,升级为轻量级锁。

竞争存在,但竞争的程度轻,自旋即等待 一会儿,另1个线程就胡释放锁。

自旋超过一定次数,轻量级锁升级为重量级所。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!