• 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
  • 相关阅读:
    BZOJ 3744 Gty的妹子序列
    BZOJ 3872 Ant colony
    BZOJ 1087 互不侵犯
    BZOJ 1070 修车
    BZOJ 2654 tree
    BZOJ 3243 向量内积
    1003 NOIP 模拟赛Day2 城市建设
    CF865D Buy Low Sell High
    CF444A DZY Loves Physics
    Luogu 4310 绝世好题
  • 原文地址:https://www.cnblogs.com/naray/p/15449193.html
Copyright © 2020-2023  润新知