你知道Synchronized修饰方法和代码块有什么区别吗

1,598 阅读3分钟

Sychronized修饰方法和代码块有什么区别

sychronized是java中的同步关键字,可以用来加锁保证线程的同步执行,是并发编程的利器,那么sychronized在方法上和代码块有什么区别呢?

sychronized作用

  • 作用

    sychronized可以修饰实例方法、静态方法、代码块等,实现加锁,保证线程的同步,保证同步的功能是通过加锁来实现,获取到锁的线程才可以继续执行,没有获取到锁的线程会进行等待

  • 作用范围

    • 实例方法

      sychronized修饰实例方法,锁对象为该类的实例对象

    • 静态方法

      sychronized修饰静态方法,锁对象为该类对象

    • 代码块

      sychronized修饰代码块,则锁对象为代码块中指定的对象(任何对象都可以作为锁)

      • 注意:如果需要保证锁的唯一性,则需要保证锁对象的唯一性,否则无法保证所有线程的同步
  • 实现原理

    sychronized底层是通过Monitor对象来实现锁机制的,在sychronized关键之前,线程会先执行monitorenter指令(JVM指令),则先去获取锁,如果可以获取到锁,则将Monitor对象中的持有锁owner修改(CAS操作)为当前线程id(owner表示持有锁的线程),并将state变量+1(state是当前线程获取锁的次数,保证了锁的可重入性)。其他的没有获取到锁的线程会进入到Monitor对象的EntryList队列中等待。当前线程在退出sychronized的范围时,会执行monitorexit指令,会将state-1,如果state=0,则释放锁,将Monitor对象中的owner重置,并唤醒等待队列中的线程继续抢夺锁。

  • Monitor结构

     ObjectMonitor() {
        ......
        // 用来记录该线程获取锁的次数
        _count        = 0;
         // 锁的重入次数
        _recursions   = 0;
        // 指向持有ObjectMonitor对象的线程
        _owner        = NULL;
        // 存放处于wait状态的线程队列
        _WaitSet      = NULL;
        // 存放处于等待锁block状态的线程队列
        _EntryList    = NULL ;
        ......
      }
    

sychronized方法和代码块区别

  • sychronized代码块

    修饰代码块,则在编译器编译之后,会给synchronized关键字之前和之后加上monitorentermonitorexit指令,获取锁和释放锁

    void test(Foo f) {
    	sychronized(f) {
    		dosomething();
    	}
    }
    
    // 字节码指令
    Method void test(Foo)
    0: aload_1              // 将对象f入栈
    1: dup                  // 复制栈顶元素             
    2: astore_2             
    3: monitorenter        //申请获得对象的内置锁
    4: aload_0
    5: invokevirtual #5    // 调用dosomething方法
    8: aload_2
    9: monitorexit         //释放对象内置锁
    10: goto          18   // 方法正常退出,跳转到18返回
    13: astore_3		   // 从这部开始是异常路径,当方法出现异常的流程
    14: aload_2				// 将异常对象入栈
    15: monitorexit         //释放对象内置锁,退出同步
    16: aload_3
    17: athrow              // 将异常对象重新抛出给test()方法的调用者
    18: return				// 方法正常返回
    
  • sychronized修饰方法

    修饰方法和使用代码块的底层原理都是相同的,就是使用的位置有所区别。

    方法级的同步是隐式的,对于sychronized修饰的方法来讲,在生成的class文件的方发表中会有标识ACC_SYCHRONIZED访问标识,声明该方法是否为一个同步方法,当线程访问到方法的时候,如果发现有该标识,则会先获取锁,在方法执行完成之后会释放锁。相当于在方法前和方法后隐式的增加了monitorentermonitorexit指令

  • 当持有锁之后,如果发生异常如何处理?

    编译器需要保证每个monitorenter指令都会对应一个monitorexit指令,无论方法正常结束还是异常结束。所以编译器会有一个异常处理器,当方法发生未处理的异常之后,方法退出,但是编译器在异常处理器中执行monitorexit指令将锁释放。即如果一个线程持有锁之后发生未处理的异常中断,则锁还是谁正常释放。