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

 

posted @ 2021-10-27 17:31  naray  阅读(318)  评论(0编辑  收藏  举报