Java synchronized实现原理
一、简介
synchronized是互斥同步的同步机制,互斥同步又称堵塞同步。synchronized在多线程环境下,其中一条线程获得锁,其他线程需要堵塞等待持有锁的线程释放锁。
synchronized是块结构的同步语法,synchronized需要指定对象参数,对参数的引用就是reference。如果,synchronized没有指定对象,Java编译器通过synchronized修饰的方法检查synchronized修饰的是对象方法还是类方法,分别为对象锁和类锁。
public class SynchronizedTest { private static final Vector<Integer> mVector = new Vector<>(); /** * synchronized 修饰静态方法 * 类锁 */ public synchronized static void printData1() { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } /** * synchronized 修饰对象方法 * 对象锁 */ public synchronized void printData2() { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } /** * synchronized 保证块中代码是互斥同步 * 对象锁 * * synchronized 也可以修饰类,如:synchronized (Vector.class) {} */ public void printData() { synchronized (mVector) { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } } }
二、synchronized实现原理
将上面代码class文件反汇编后,代码太长截取部分:
public void printData(); Code: 0: getstatic #2 // Field mVector:Ljava/util/Vector; 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: getstatic #2 // Field mVector:Ljava/util/Vector; 12: invokevirtual #3 // Method java/util/Vector.size:()I 15: if_icmpge 37 18: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 21: getstatic #2 // Field mVector:Ljava/util/Vector; 24: iload_2 25: invokevirtual #5 // Method java/util/Vector.get:(I)Ljava/lang/Object; 28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 31: iinc 2, 1 34: goto 8 37: aload_1 38: monitorexit 39: goto 47 42: astore_3 43: aload_1 44: monitorexit 45: aload_3 46: athrow 47: return
synchronized是通过monitorenter和monitorexit字节码指令实现的,monitorenter是获取指定对象的锁,monitorexit是释放指定对象的锁,分别对应Java虚拟机字节码指令lock和unlock。Java虚拟机字节码指令lock和unlock都是原子操作,所以,synchronized具有原子性。
monitorenter和monitorexit字节码指令需要指定reference类型的参数来说明锁定和解锁的对象。
synchronized需要指定对象参数,对象参数引用就是monitorenter和monitorexit字节码指令需要的reference。
执行monitorenter字节码指令时,执行字节码指令的线程需要尝试获取指定对象的锁,如果,获取指定的对象没有锁,又或者获取指定对象的锁已经被该线程锁定,那么,该对象锁的计数器加1。如果,指定对象的锁已经被其他线程持有,线程获取锁失败,那么,获取该对象锁失败的线程需要堵塞等待,直到持有该对象锁的线程释放锁为止。
执行monitorexit字节码指令时,指定对象锁的计数器减1,直到计数器到0,持有该对象锁的线程释放该对象的锁。
通过monitorenter和monitorexit字节码指令的机制,可以推测出synchronized的两点结论:
-
- synchronized修饰的同步代码块,允许线程重复获取指定对象的锁,只是对象的锁的计数器加1。也就是说,获取该对象锁的线程允许重入获取对象的锁,而不用担心死锁问题。
- synchronized修饰的同步代码块,一旦指定对象的锁被线程持有,其他获取该对象锁的线程必须进入堵塞等待状态,等待持有该对象锁的线程释放该对象锁。也就是说,一旦指定对象的锁被线程持有,其他获取该对象锁的线程不能强制已持有该对象锁的线程释放锁,也不能将获取该对象锁失败的被堵塞的线程中止或者超时退出等待。
三、synchronized性能
synchronized是重量级锁,在主流的Java虚拟机的线程模型是将Java虚拟机的线程映射到系统内核线程之上的,线程的堵塞和唤醒需要操作系统完成,Java虚拟机的线程是用户线程,操作系统调度线程需要内核态和用户态之间切换状态,这部分很花费内核执行时间。比如:让synchronized修饰getter()和setter()方法,内核态和用户态的状态切换比方法的执行时间都长。
四、synchronized代码块出现异常会释放锁码?
答案是肯定的会释放锁。
验证方式,两条线程访问统一共享对象,在第一条线程执行出错后,第二条线程是否能正常访问,代表能获得锁:
public class Main { public static void main(String[] args) { Test test = new Test(); Thread thread1 = new Thread(test); Thread thread2 = new Thread(test); thread1.start(); thread2.start(); } } class Test implements Runnable { public synchronized void print1() throws Exception { System.out.println(Thread.currentThread().getName() + " print 1"); printException(); System.out.println(Thread.currentThread().getName() + " print 1 end"); } public synchronized void printException() throws Exception { System.out.println(Thread.currentThread().getName() + " print exception"); throw new Exception(Thread.currentThread().getName() + " this is a test!"); } public void run() { try { print1(); } catch (Exception e) { System.out.println(Thread.currentThread().getName() + " exception once"); } try { Thread.sleep(10000L); System.out.println(Thread.currentThread().getName() + " exit"); } catch (Exception e) { System.out.println(Thread.currentThread().getName() + " some exception"); } } }
输出:
Thread-0 print 1 Thread-0 print exception Thread-0 exception once Thread-1 print 1 Thread-1 print exception Thread-1 exception once Thread-0 exit Thread-1 exit Process finished with exit code 0