• 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
  • 相关阅读:
    Linux内核之旅 链表实现
    Linux内核之旅 List_entry()
    希尔排序
    华为2013校园招聘上机笔试题 ---2 字符串处理转换
    编程求凸包点集
    练习一:SQLite基本操作
    java实现单链表反转
    android-数据存储之外部file存储(sdcard)
    android-数据存储之手机内部file存储
    android-数据存储之SharedPreferences
  • 原文地址:https://www.cnblogs.com/naray/p/15449193.html
Copyright © 2020-2023  润新知