Java同步处理底层实现(monitor 可重入锁)

对于Java同步处理可以参考这篇博客:https://blog.csdn.net/sophia__yu/article/details/83990755

但是这些处理方法的底层实现是怎样呢?
接下里将会解释同步代码块、同步方法、全局锁的底层实现。

同步代码块底层实现:

首先看一个简单的同步代码块:

/////同步代码块底层实现

public class DiCeng {

    public static void main(String[] args)
    {
        Object obj=new Object();
        synchronized (obj)  //把obj对象锁住
        {
            System.out.println("hello sun");
        }
    }
}

先将Java程序编译:javac -encoding UTF-8 DiCeng.java ;
然后进行反编译:javap -c DiCeng ;
下面代码是反编译后底层代码:

 public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: astore_1
       8: aload_1
       9: dup
      10: astore_2
      11: monitorenter         ////注意:monitorenter进入同步代码块
      12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      15: ldc           #4                  // String hello sun
      17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: aload_2
      21: monitorexit      /////注意:monitorexit正常退出同步代码块进行解锁
      22: goto          30
      25: astore_3
      26: aload_2
      27: monitorexit    /////注意:monitorexit异常退出同步块进行解锁
      28: aload_3
      29: athrow
      30: return
    Exception table:
       from    to  target type
          12    22    25   any
          25    28    25   any

在反编译后代码后,我们可以看见1个monitorenter,2个monitorexit ,这些代表什么意思呢?解释如下:
在进入同步代码块时,首先需要执行monitorenter,退出时执行monitorexit进行解锁。
使用synchronized实现同步,关键点在于获取对象的监视器monitor对象,当获取到monitor对象后,才可以执行同步代码块即执行monitorenter操作,否则,就没有monitorenter操作并且等待。获取monitor对象是互斥操作,即同一时刻只能有一个线程可以获取到要锁对象的monitor监视器。


有1个monitorenter,2个monitorexit是因为:通常一个monitor指令会包含多个monitorexit指令。是因为JVM虚拟机需要确保所获取的锁不论是正常执行路径或者异常执行路径都能正确解锁,
否则当发生异常突然退出时没有解锁操作,那么其他线程也获取不到这个对象的锁,其他线程就无法正确执行。

同步方法底层实现:

////同步方法
public class DiCeng {

    public static void main(String[] args)
    {
        new DiCeng().print();
    }
    synchronized public void print()
    {
        System.out.println("hello sun");
    }
}

先将Java程序编译:javac -encoding UTF-8 DiCeng.java ;
然后进行反编译:javap -v DiCeng ;(注意:方法是javap -v )
反编译后代码如下:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class CODE/多线程/DiCeng
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method print:()V
        10: return
      LineNumberTable:
        line 49: 0
        line 50: 10

  public synchronized void print();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED    //ACC_SYNCHRONIZED标识
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String hello sun
         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 53: 0
        line 54: 8

当使用synchronized标记方法时,字节码会出现访问标记ACC_SYNCHRONIZED。该标记表示:在进入该方法时,JVM需要进行 monitorenter操作,获取对象的监视器monitor;在退出该方法时无论是正常返回,JVM均需要进行monitorexit操作。

不论是同步代码块还是同步方法,都要执行monitorenter操作获取该对象的monitor监视器,如果对象的monitor的计数器为0,表示该对象没有被其他线程所持有,此时JVM会将该 锁对象的持有线程设置为当前线程,并且将monitor计数器加1。
在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,JVM可以将计数器再次加1(可重入锁),否则需要等待,直到持有线程释放该锁。

可重入锁:
可重入锁指当前线程获取到该对象的锁后,该对象的所有同步方法这个线程都可以访问。因为该对象的锁已经被这个线程获取到,那么对象里面的东西这个线程都可以用,就比如说我用锁打开门后,门里的东西我都能用,门外的人用不了门里面的东西。

//可重入锁
class Mythread4 implements Runnable
{
    public void run()
    {
        print1();
        print2();
    }

    ///线程1先获取到Mythread4实例化对象的锁,monitor监视器变为1
   public synchronized void print1()
   {
       if(Thread.currentThread().getName().equals("线程1"));
       {
           print2(); //进入同步方法print2,monitor不为0,但是锁持有的线程是当前线程1,将mobitor变为2
           while(true) {
              // 一个循环使线程1一直不释放锁,那么线程2就无法获取锁
           }
       }
   }

   ////线程2启动后,该方法是同步方法,但是线程1没有释放锁,monitor不为0,所以线程2进不了该方法
   public synchronized void print2()
   {
       if(Thread.currentThread().getName().equals("线程1"))
       {
           System.out.println("线程1进入另一个同步方法");
       }
       else
       {
           System.out.println("线程1没有释放锁,线程2进入同步方法");
       }
   }
}
public class DiCeng
{
    public static void main(String[] args) throws InterruptedException {
        Mythread4 thread=new Mythread4();
        new Thread(thread,"线程1").start();
        Thread.sleep(5000);  //确保线程1先获取到锁
        new Thread(thread,"线程2").start();

    }
}

在这里插入图片描述
线程1可以进入其他同步方法,然后一直在循环里面,线程2无法获取到锁。

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/84023597
今日推荐