zoukankan      html  css  js  c++  java
  • Java:Synchronized实现原理

    Java:Synchronized实现原理

    一、Synchronized实现同步代码块:

    先来看个两个简单的程序

    // 代码一
    public class OutOfSyncMonitor {
        private int data = 0;
        public void method1() {
                try {
                    data = data + 1;
              // 线程休眠1s,使其在还未执行完毕时,cpu进行时间片轮转
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
        }
    
        public static void main(String[] args) {
            final OutOfSyncMonitor monitor = new OutOfSyncMonitor();
            new Thread(monitor::method1).start();
            new Thread(monitor::method1).start();
        }
    }
    
    // 代码二
    public class OutOfSyncMonitor {
        private int data = 0;
        public void method1() {
            synchronized (this) {
                try {
                    data = data + 1;
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            final OutOfSyncMonitor monitor = new OutOfSyncMonitor();
            new Thread(monitor::method1).start();
            new Thread(monitor::method1).start();
        }
    }
    

    可以很容易的看出代码一是线程不安全的,代码二在method1方法中采用synchronized实现同步代码块保证了线程安全,我们再来看个代码

    // 代码三
    public class OutOfSyncMonitor {   
        // 所有的OutOfSyncMonitor的对象操作的多是同一个data
        private static int data = 0;
        public void method1() {
            synchronized (this) {
                try {
                    data = data + 1;
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            OutOfSyncMonitor monitor = new OutOfSyncMonitor();
            OutOfSyncMonitor monitor1 = new OutOfSyncMonitor();
            new Thread(monitor::method1).start();
            new Thread(monitor1::method1).start();
        }
    }
    

    在method1方法中还是采用synchronized实现同步代码块,期待保证数据安全,但是令人意外的时输出的data并不是我们所想要的,这样的实现并不能保证线程安全。为什么呢?我们来分析下

    synchronized是对同步代码块进行加锁,线程再去争抢锁来达到同步的目的。但是如果锁不唯一呢?换句话来说就是不是同一把锁呢?这显然达不到设计的初衷。而代码三就是这样的,在method1中锁的对象不唯一

    public static void main(String[] args) {
            OutOfSyncMonitor monitor = new OutOfSyncMonitor();
            OutOfSyncMonitor monitor1 = new OutOfSyncMonitor();
            new Thread(monitor::method1).start();        
            new Thread(monitor1::method1).start();
    }
    

    在这里创建了两个OutOfSyncMonitor对象,两个线程分别执行它们的method1方法,而在method1中synchronized的对象时this,导致了我们非期望的效果。为什么会出现这种情况呢?我们看下线程的堆栈信息(jstack )

    "Thread-1" #13 prio=5 os_prio=0 tid=0x0000022a97b14000 nid=0x2d60 waiting on condition [0x000000e8343ff000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at java.lang.Thread.sleep(Thread.java:340)
            at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
            at cn.itcod.thread.OutOfSyncMonitor.method1(OutOfSyncMonitor.java:14)
            - locked <0x000000076b933780> (a cn.itcod.thread.OutOfSyncMonitor)
            at cn.itcod.thread.OutOfSyncMonitor$$Lambda$2/1096979270.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)"Thread-0" #12 prio=5 os_prio=0 tid=0x0000022a97b10000 nid=0x3cf8 waiting on condition [0x000000e8342ff000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at java.lang.Thread.sleep(Thread.java:340)
            at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
            at cn.itcod.thread.OutOfSyncMonitor.method1(OutOfSyncMonitor.java:14)
            - locked <0x000000076b933770> (a cn.itcod.thread.OutOfSyncMonitor)
            at cn.itcod.thread.OutOfSyncMonitor$$Lambda$1/1324119927.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    

    从上可以看出每个线程争抢的monitor关联引用是彼此独立的,这也就导致了锁失败的原因。可以采取以下方法来实现我们的预期效果

    public class OutOfSyncMonitor {
        private static int data = 0;
        public void method1() {
            synchronized (OutOfSyncMonitor.class) {
                try {
                    data = data + 1;
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }  
        public static void main(String[] args) {// 省略}
     }
    

    我们通过锁类来实现同步代码块。这就有两个概念:对象锁 和 类锁

      1、对象级别的锁是锁定在对象上的一把锁,只有在线程在访问的是同一个对象时,才会通过竞争来获取得锁。

      2、类级别的锁是锁定在类上的一把锁,当线程执行这类的方法时,不管调用的对象是否是同一个,多会产生锁竞争来获得锁。

    二、Synchronized的实现原理
    我们使用javap对代码一和代码二进行反汇编看下JVM指令:

    public void method1();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=4, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter
             4: aload_0
             5: aload_0
             6: getfield      #2                  // Field data:I
             9: iconst_1
            10: iadd
            11: putfield      #2                  // Field data:I
            14: getstatic     #3                  // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
            17: lconst_1
            18: invokevirtual #4                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
            21: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
            24: aload_0
            25: getfield      #2                  // Field data:I
            28: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
            31: goto          39
            34: astore_2
            35: aload_2
            36: invokevirtual #8                  // Method java/lang/InterruptedException.printStackTrace:()V
            39: aload_1
            40: monitorexit
            41: goto          49
            44: astore_3
            45: aload_1
            46: monitorexit
            47: aload_3
            48: athrow
            49: return
    

    我们可以看到代码二的反汇编第9行和第40行有两个特别的指令monitorenter和monitorexit,并且是成对出现的(有些时候会出现一个monitorenter多个monitorexit,但是每一个monitorexit之前必有对应的monitor enter,这是肯定的。【Java高并发编程详解:汪文君】);在不加锁的代码一中却没有出现这两个指令。

    public void method1();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=2, args_size=1
             0: aload_0
             1: aload_0
             2: getfield      #2                  // Field data:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field data:I
            10: getstatic     #3                  // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
            13: lconst_1
            14: invokevirtual #4                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
            17: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
            20: aload_0
            21: getfield      #2                  // Field data:I
            24: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
            27: goto          35
            30: astore_1
            31: aload_1
            32: invokevirtual #8                  // Method java/lang/InterruptedException.printStackTrace:()V
            35: return
    

    这是synchronized关键字包裹monitor enter和monitor exit两个JVM指令,它能够保证在如何时候线程执行到monitor enter成功之前必须从主内存中获取数据,而不是从缓存中,在monitor exit运行成功后,共享变量被更新后的值必须刷入主内存。下面来讲下这两个指令
      1、monitor enter
    每个对象多有与monitor与之关联,一个monitor的lock锁只能被一个线程在同一时间获取,在一个线程尝试获取与锁对象相关联的monitor时会发生以下几件事情。
    如果monitor的计数器为0,意味着该monitor的lock锁还没有被获取,当一个线程获的后会立刻对该计数器+1,这样就代表这该monitor被占有
    如果一个已经拥有该monitor所有权的线程重入,则会导致monitor的计数器再次被累加
    如果monitor已经被其他线程占有,其他线程尝试获取该monitor的所有权时,被陷入到阻塞状态,知道monitor计数器变为0,才再次尝试获取monitor所有权
      2、monitor exit
      释放对monitor的所有权,前提是曾经获得过所有权。释放的过程较为简单,就是将monitor的计数器-1,如果计数器的结果为0。则代表这线程失去了对该monitor的所有权,与此同时被该monitor block的线程将再次尝试获取该monitor的所有权。

    PS:如有不足,还望大佬斧正

  • 相关阅读:
    22天学习java基础笔记之day08
    22天学习java基础笔记之day07
    22天学习java基础笔记之day06
    22天学习java基础笔记之day05
    架构师_设计模式_行为型_迭代器
    架构师_设计模式_行为型_命令模式
    架构师_设计模式_行为型_责任链模式
    架构师_设计模式_行为型_模板方法
    架构师_设计模式_结构型_装饰器模式
    架构师_设计模式_结构型_桥接模式
  • 原文地址:https://www.cnblogs.com/itcod/p/14613174.html
Copyright © 2011-2022 走看看