- synchronize 修饰的同步代码块:使用monitorenter 和 monitorexit 指令实现;
- synchronize 修饰的方法并没有 monitorenter 和 monitorexit 指令 ,而取代之的是 ACC_SYNCHRONIZED标识,该标志指明了该方法是一个同步方法,从而执行相应的同步调用。
(1)synchronized修饰的方法
public synchronized void method() { int i = 0; }
javap反编译后:
public synchronized void method(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=1, locals=2, args_size=1 0: iconst_0 1: istore_1 2: return LineNumberTable: line 6: 0 line 7: 2 LocalVariableTable: Start Length Slot Name Signature 0 3 0 this Lcom/lock/SyncTest; 2 1 1 i I
对于同步方法,JVM会讲方法设置 ACC_SYNCHRONIZED 标志,调用的时候 JVM 根据这个标志判断是否是同步方法。
(2)synchronized修改的代码块
public void method() { synchronized (this) { int i = 0; } }
javap反编译后:
public void method(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: iconst_0 5: istore_2 6: aload_1 7: monitorexit 8: goto 14 11: aload_1 12: monitorexit 13: athrow 14: return Exception table: from to target type 4 8 11 any 11 13 11 any LineNumberTable: line 6: 0 line 7: 4 line 6: 6 line 9: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/lock/SyncTest;
ObjectMonitor() { _header = NULL; _count = 0; // 记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; // 记录当前持有锁的线程ID _WaitSet = NULL; // 等待池:处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 锁池:处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
ObjectMonitor 中有两个队列,_WaitSet 和 _EntryList ,用来保存ObjectWaiter 对象列表,每个等待锁的线程都会被封装成ObjectWaiter 对象,_owner 指向持有ObjectMonitor 对象的线程,当多个线程同时访问同一同步代码块或者同步方法时,首先会进入 _EntryList 队列,当线程获取到monitor 后进入_Owner 区域并把 monitor中的 _Owner 变量设置为当前线程,同时monitor 中的计数器count 加1,若线程调用wait() 方法,将释放当前持有的monitor,_owner变量恢复为null,count 自减 1 ,同时该线程进入_WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:
- _owner 记录当前持有锁的线程ID
- _EntryList 是一个队列,记录所有阻塞等待锁的线程(阻塞队列,锁池)
- _WaitSet 也是一个队列,记录调用 wait() 方法并还未被通知的线程(等待池)
public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; static int j=0; @Override public void run() { for(int j=0;j<1000000;j++){ //this,当前实例对象锁 synchronized(this){ i++; increase();//synchronized的可重入性 } } } public synchronized void increase(){ j++; } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
正如代码所演示的,在获取当前实例对象锁后进入synchronized代码块执行同步代码,并在代码块中调用了当前实例对象的另外一个synchronized方法,再次请求当前实例锁时,将被允许,进而执行方法体代码,这就是重入锁最直接的体现,需要特别注意另外一种情况,当子类继承父类时,子类也是可以通过可重入锁调用父类的同步方法。注意由于synchronized是基于monitor实现的,因此每次重入,monitor中的计数器仍会加1。