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关键字之前和之后加上
monitorenter和monitorexit指令,获取锁和释放锁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访问标识,声明该方法是否为一个同步方法,当线程访问到方法的时候,如果发现有该标识,则会先获取锁,在方法执行完成之后会释放锁。相当于在方法前和方法后隐式的增加了monitorenter和monitorexit指令 -
当持有锁之后,如果发生异常如何处理?
编译器需要保证每个
monitorenter指令都会对应一个monitorexit指令,无论方法正常结束还是异常结束。所以编译器会有一个异常处理器,当方法发生未处理的异常之后,方法退出,但是编译器在异常处理器中执行monitorexit指令将锁释放。即如果一个线程持有锁之后发生未处理的异常中断,则锁还是谁正常释放。