美文网首页Java多线程
线程基本知识之synchronized同步方法分析

线程基本知识之synchronized同步方法分析

作者: 码上就说 | 来源:发表于2018-10-23 21:12 被阅读65次

    如果问你Java中有哪些同步的机制,大家基本上都能答出来:synchronized与lock机制,synchronized机制大家都会用,在需要同步的代码块或者同步的方法上加上synchronized关键字,但是为什么加上这个关键字就能表示同步了,大家可能就不太熟悉了。

    • 问题一:为什么加上synchronized就能表示同步?

    《线程基本知识总结一》中谈到了synchronized的基本用法,我们一般使用synchronized对方法或者代码块进行同步。同步的对象可以是一个Object对象或者类。这是基本点,需要我们区分清楚。
    synchronized使用有一定的原则:锁的范围尽可能小;锁的时间尽可能短;能锁对象的时候,就不要锁类;能锁代码块的时候,就不要锁方法。
    将synchronized影响的范围缩小,这样可以保护代码,不能同步的范围过大,容易造成效率过低。
    下面通过一段代码展示一下synchronized到底是怎么工作的。
    Java代码如下:

    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    package com.mony.testThread;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     *
     * @author jeffli
     */
    public class TestMutex {
       private final static Object MUTEX = new Object();
       
       public void accessFunction() {
           synchronized(MUTEX) {
               try {
                   TimeUnit.MINUTES.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
       
       public static void main(String[] args) {
           final TestMutex mutex = new TestMutex();
           new Thread(mutex::accessFunction).start();
       }
    }
    

    javac将TestMutex.java文件编译成.class文件。


    image.png

    然后通过javap -c TestMutex可以查看汇编指令:


    image.png
    下面贴上所有的汇编指令:
    警告: 二进制文件TestMutex包含com.mony.testThread.TestMutex
    Compiled from "TestMutex.java"
    public class com.mony.testThread.TestMutex {
      public com.mony.testThread.TestMutex();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public void accessFunction();
        Code:
           0: getstatic     #2                  // Field MUTEX:Ljava/lang/Object;
           3: dup
           4: astore_1
           5: monitorenter
           6: getstatic     #3                  // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit;
           9: lconst_1
          10: invokevirtual #4                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
          13: goto          21
          16: astore_2
          17: aload_2
          18: invokevirtual #6                  // Method java/lang/InterruptedException.printStackTrace:()V
          21: aload_1
          22: monitorexit
          23: goto          31
          26: astore_3
          27: aload_1
          28: monitorexit
          29: aload_3
          30: athrow
          31: return
        Exception table:
           from    to  target type
               6    13    16   Class java/lang/InterruptedException
               6    23    26   any
              26    29    26   any
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #7                  // class com/mony/testThread/TestMutex
           3: dup
           4: invokespecial #8                  // Method "<init>":()V
           7: astore_1
           8: new           #9                  // class java/lang/Thread
          11: dup
          12: aload_1
          13: dup
          14: invokevirtual #10                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
          17: pop
          18: invokedynamic #11,  0             // InvokeDynamic #0:run:(Lcom/mony/testThread/TestMutex;)Ljava/lang/Runnable;
          23: invokespecial #12                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
          26: invokevirtual #13                 // Method java/lang/Thread.start:()V
          29: return
    
      static {};
        Code:
           0: new           #14                 // class java/lang/Object
           3: dup
           4: invokespecial #1                  // Method java/lang/Object."<init>":()V
           7: putstatic     #2                  // Field MUTEX:Ljava/lang/Object;
          10: return
    }
    

    下面的main函数可以暂时忽略,我们主要看accessFunction()函数中的关于synchronized关键是是如何执行的。


    image.png

    上面将重要的地方都标注了。

    • step1是获取MUTEX引用。
    • step2执行了monitorenter JVM指令,休眠结束之后直接goto 21
    • step3 21行下面紧接着就执行step3 monitorexit释放锁。
    • step4的monitorexit是当程序发生中断意外会执行的,保险起见,为了保证当前的monitor一定要释放。

    synchronized锁的特定是在JVM级别上控制的,JVM底层通过监视锁来实现synchronized同步的。
    注意:
    Monitorenter:每个对象都与一个monitor相关联,一个monitor的lock锁只能被一个线程在同一时间获得,一个线程尝试获得对象或者类关联的monitor所有权会执行下面的步骤:

    • 如果monitor的计数器为0,意味着monitor的lock未获得,线程获得之后立即对该计数器+1,该线程就是此monitor的所有者。

    • 如果一个线程已经有monitor所有权,再次执行到synchronized时,就是重入monitorenter,这时候monitor计数器再次累加。

    • monitor被某一线程拥有,其他线程尝试获取该monitor所有权时,会进入阻塞状态,直到monitor计数器为0,才能获取monitor所有权。

    Monitorexit:执行Monitorexit,就是释放monitor所有权,将monitor计数器-1,如果monitor计数器为0,该线程不再拥有对monitor的所有权。

    拓展

    在JDK的不断迭代优化中,synchronized性能获得极大的提升,特别是偏向锁的实现,使synchronized不再是笨重且性能低下的锁了。

    • JVM对synchronized的优化主要在于对monitor的加锁、解锁上。
    • JDK6之后优化了synchronized提供了:偏向锁、轻量级锁、重量级锁,还提供自动的升级和降级功能。
    • 偏向锁:偏向锁是为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。
      偏向锁的功能主要是在锁对象头中有一个ThreadId字段,当第一个线程访问锁时,如果该锁没有被其他线程访问过,ThreadId为null,JVM会让该线程持有偏向锁,将ThreadId置为该线程的ID。
      下一次获取锁时,会判断当前的ID是否与锁对象的ThreadId一致,如果一致,线程不会重复获取锁,提高了程序的运行效率。
    • 如果出现锁竞争的情况,偏向锁被撤销升级为轻量级锁;
    • 如果资源的竞争非常激烈,会升级为重量级锁。
    • 偏向锁可以降低无竞争开销,它不是互斥锁,不存在线程竞争的情况,省去了再次判断同步的步骤,性能得到了提升。

    下一章介绍 Lock锁。
    这篇文章将synchronized和lock之间的区别总结得非常好,《 synchronized 的局限性 与 Lock 的优点》

    相关文章

      网友评论

        本文标题:线程基本知识之synchronized同步方法分析

        本文链接:https://www.haomeiwen.com/subject/fnyqzftx.html