[万字长文,建议收藏]关于Synchronized锁升级,你该了解这些

前言

毫无疑问,synchronized是我们用过的第一个并发关键字,很多博文都在讲解这个技术。不过大多数讲解还停留在对synchronized的使用层面,其底层的很多原理和优化,很多人可能并不知晓。 因此本文将通过对synchronized的大量C源码分析,让大家对他的了解更加透彻点。

本篇将从为什么要引入synchronized,常见的使用方式,存在的问题以及优化部分这四个方面描述,话不多说,开始表演。

可见性问题及解决

概念描述

指一个线程对共享变量进行修改,另一个能立刻获取到修改后的最新值。

代码展示

类:


public 
class 
Example1 
{

    //1.创建共享变量
    private static boolean flag = true ;

    public static void main (String [ ] args ) throws Exception {
        //2.t1空循环,如果flag为true,不退出
       Thread t1 = new Thread ( new Runnable ( ) {
           @Override
            public void run ( ) {
                while ( true ) {
                    if ( !flag ) {
                       System .out . println ( "进入if" ) ;
                        break ;
                    }
                }
            }
        } ) ;
       t1 . start ( ) ;

       Thread . sleep ( 2000L ) ;
        //2.t2修改flag为false
       Thread t2 = new Thread ( new Runnable ( ) {
           @Override
            public void run ( ) {
               flag = false ;
               System .out . println ( "修改了" ) ;
            }
        } ) ;

       t2 . start ( ) ;
    }
}

运行结果:

分析

这边先要了解下Java的内存模式,不明白的可点击传送门,todo。

下图线程t1,t2从主内存分别获取flag=true,t1空循环,直到flag为false的时候退出循环。t2拿到flag的值,将其改为false,并写入到主内存。此时主内存和线程t2的工作内存中flag均为false,但是线程t1工作内存中的flag还是true,所以一直退不了循环,程序将一直执行。

synchronized如何解决可见性

首先我们尝试在t1线程中加一行打印语句,看看效果。

代码:


public 
class 
Example1 
{

    //1.创建共享变量
    private static boolean flag = true ;

    public static void main (String [ ] args ) throws Exception {
        //2.t1空循环,如果flag为true,不退出
       Thread t1 = new Thread ( new Runnable ( ) {
           @Override
            public void run ( ) {
                while ( true ) {
                    //新增的打印语句
                   System .out . println (flag ) ;
                    if ( !flag ) {
                       System .out . println ( "进入if" ) ;
                        break ;
                    }
                }
            }
        } ) ;
       t1 . start ( ) ;

       Thread . sleep ( 2000L ) ;
        //2.t2修改flag为false
       Thread t2 = new Thread ( new Runnable ( ) {
           @Override
            public void run ( ) {
               flag = false ;
               System .out . println ( "修改了" ) ;
            }
        } ) ;

       t2 . start ( ) ;
    }
}

运行结果:

我们发现if里面的语句已经打印出来了,线程1已经感知到线程2对flag的修改,即这条打印语句已经影响了可见性。这是为啥?

答案就是println中,我们看下源码:

println有个上锁的过程,即操作如下:

1.获取同步锁。

2.清空自己工作内存上的变量。

3.从主内存获取最新值,并加载到工作内存中。

4.打印并输出。

所以这里解释了为什么线程t1加了打印语句之后,t1立刻能感知t2对flag的修改。因为每次打印的时候其都从主内存上获取了最新值,当t2修改的时候,t1立刻从主内存获取了值,所以进入了if语句,并最终能跳出循环。

synchronized的原理就是清空自己工作内存上的值,通过将主内存最新值刷新到工作内存中,让各个线程能互相感知修改。

原子性问题及解决 

概念描述

在一次或多个操作中,要不所有操作都执行,要不所有操作都不执行。

代码展示

类:


public 
class 
Example2 
{

    //1.定义全局变量number
    private static int number = 0 ;

    public static void main (String [ ] args ) throws Exception {
       Runnable runnable = ( ) - > {
            for (int i = 0 ; i < 10000 ; i ++ ) {
                   number ++ ;
            }
        } ;
        //2.t1让其自增10000
       Thread t1 = new Thread (runnable ) ;
       t1 . start ( ) ;

        //3.t2让其自增10000
       Thread t2 = new Thread (runnable ) ;
       t2 . start ( ) ;

        //4.等待t1,t2运行结束
       t1 . join ( ) ;
       t2 . join ( ) ;
       System .out . println ( "number=" + number ) ;
    }
}

运行结果:

分析

每个线程执行的逻辑是循环1万次,每次加1,那我们希望的结果是2万,但是实际上结果是不足2万的。我们先用javap命令反汇编,我们看到很多代码,但是number++涉及的指令有四句,具体看第二张图。



如果有多条线程执行这段number++代码,当前number为0,线程1先执行到iconst_1指令,即将执行iadd操作,而线程2执行到getstatic指令,这个时候number值还没有改变,所以线程2获取到的静态字段是0,线程1执行完iadd操作,number变为1,线程2执行完iadd操作,number还是1。这个时候就发现问题了,做了两次number++操作,但是number只增加了1。

并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半的时候,另外一个线程也有可能来操作共享变量,这个时候就出现了问题。

synchronized如何解决原子性问题

在上面的分析中,我们已经知道发生问题的原因,number++是由四条指令组成,没有保证原子操作。所以,我们只要将number++作为一个整体就行,即保证他的原子性。具体代码如下:


public 
class 
Example2 
{

    //1.定义全局变量number
    private static int number = 0 ;
    //新增一个静态变量object
    private static Object object = new Object ( ) ;

    public static void main (String [ ] args ) throws Exception {
       Runnable runnable = ( ) - > {
            for (int i = 0 ; i < 10000 ; i ++ ) {
                //将number++的操作用object对象锁住
                synchronized ( object ) {
                   number ++ ;
                }
            }
        } ;
        //2.t1让其自增10000
       Thread t1 = new Thread (runnable ) ;
       t1 . start ( ) ;

        //3.t2让其自增10000
       Thread t2 = new Thread (runnable ) ;
       t2 . start ( ) ;

        //4.等待t1,t2运行结束
       t1 . join ( ) ;
       t2 . join ( ) ;
       System .out . println ( "number=" + number ) ;
    }
}


我们看到最终number为20000,那为什么要加上synchronized,结果就正确了?我们再反编译下Example2,可以看到在四行指令前后分别有monitorenter和monitorexist,线程1在执行中间指令时,其他线程不可以进入monitorenter,需要等线程1执行完monitorexist,其他进程才能继续monitorenter,进行自增操作。

//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!

有序性问题及解决

概念描述

代码中程序执行的顺序,Java在编译和运行时会对代码进行优化,这样会导致我们最终的执行顺序并不是我们编写代码的书写顺序。

代码展示

咱先来看一个概念, 重排序,也就是语句的执行顺序会被重新安排。其主要分为三种:

1.编译器优化的重排序:可以重新安排语句的执行顺序。

2.指令级并行的重排序:现代处理器采用指令级并行技术,将多条指令重叠执行。

3.内存系统的重排序:由于处理器使用缓存和读写缓冲区,所以看上去可能是乱序的。

上面代码中的a = new A();可能被被JVM分解成如下代码:


// 可以分解为以下三个步骤

1 memory = allocate ( ) ; // 分配内存 相当于c的malloc
2 ctorInstanc (memory ) //初始化对象
3 s =memory //设置s指向刚分配的地址


 
// 上述三个步骤可能会被重排序为 1-3-2,也就是:

1 memory = allocate ( ) ; // 分配内存 相当于c的malloc
3 s =memory //设置s指向刚分配的地址
2 ctorInstanc (memory ) //初始化对象


一旦假设发生了这样的重排序,比如线程A在执行了步骤1和步骤3,但是步骤2还没有执行完。这个时候线程B进入了第一个语句,它会判断a不为空,即直接返回了a。其实这是一个未初始化完成的a,即会出现问题。

synchronized如何解决有序性问题

给上面的三个步骤加上一个synchronized关键字,即使发生重排序也不会出现问题。线程A在执行步骤1和步骤3时,线程B因为没法获取到锁,所以也不能进入第一个语句。只有线程A都执行完,释放锁,线程B才能重新获取锁,再执行相关操作。

synchronized的常见使用方式

修饰代码块(同步代码块)


synchronized 
(
object
) 
{

      //具体代码
}

修饰方法

synchronized 
void 
test
(
)
{

  //具体代码
}

synchronized不能继承?(插曲)

父类A:


public 
class 
A 
{

   synchronized void test ( ) throws Exception {
        try {
           System .out . println ( "main 下一步 sleep begin threadName="
                    + Thread . currentThread ( ) . getName ( ) + " time="
                    + System . currentTimeMillis ( ) ) ;
           Thread . sleep ( 5000 ) ;
           System .out . println ( "main 下一步 sleep end threadName="
                    + Thread . currentThread ( ) . getName ( ) + " time="
                    + System . currentTimeMillis ( ) ) ;
        } catch (Exception e ) {
           e . printStackTrace ( ) ;
        }
    }
}


子类B:(未重写test方法)


public 
class 
B 
extends 
A 
{


}


子类C:(重写test方法)


public 
class 
C 
extends 
A 
{


   @Override
    void test ( ) throws Exception {
        try {
           System .out . println ( "sub 下一步 sleep begin threadName="
                    + Thread . currentThread ( ) . getName ( ) + " time="
                    + System . currentTimeMillis ( ) ) ;
           Thread . sleep ( 5000 ) ;
           System .out . println ( "sub 下一步 sleep end threadName="
                    + Thread . currentThread ( ) . getName ( ) + " time="
                    + System . currentTimeMillis ( ) ) ;
        } catch (Exception e ) {
           e . printStackTrace ( ) ;
        }
    }
}


线程A:


public 
class 
ThreadA 
extends 
Thread 
{

    private A a ;

    public void setter   ( A a ) {
        this .a = a ;
    }

   @Override
    public void run ( ) {
        try {
           a . test ( ) ;
        } catch (Exception e ) {

        }
    }
}


线程B:


public 
class 
ThreadB 
extends 
Thread 
{

    private B b ;
    public void setB ( B b ) {
        this .b =b ;
    }

   @Override
    public void run ( ) {
        try {
           b . test ( ) ;
        } catch (Exception e ) {

        }
    }
}


线程C:


public 
class 
ThreadC 
extends 
Thread
{

    private C c ;
    public void setC ( C c ) {
        this .c =c ;
    }

   @Override
    public void run ( ) {
        try {
           c . test ( ) ;
        } catch (Exception e ) {

        }
    }
}


测试类test:


public 
class 
test 
{

    public static void main (String [ ] args ) throws Exception {
        A a = new A ( ) ;
       ThreadA A1 = new ThreadA ( ) ;
        A1 . setter (a ) ;
        A1 . setName ( "A1" ) ;
        A1 . start ( ) ;
       ThreadA A2 = new ThreadA ( ) ;
        A2 . setter (a ) ;
        A2 . setName ( "A2" ) ;
        A2 . start ( ) ;
        A1 . join ( ) ;
        A2 . join ( ) ;

       System .out . println ( "=============" ) ;
        B b = new B ( ) ;
       ThreadB B1 = new ThreadB ( ) ;
        B1 . setB (b ) ;
        B1 . setName ( "B1" ) ;
        B1 . start ( ) ;
       ThreadB B2 = new ThreadB ( ) ;
        B2 . setB (b ) ;
        B2 . setName ( "B2" ) ;
        B2 . start ( ) ;
        B1 . join ( ) ;
        B2 . join ( ) ;
       System .out . println ( "=============" ) ;

        C c = new C ( ) ;
       ThreadC C1 = new ThreadC ( ) ;
        C1 . setName ( "C1" ) ;
        C1 . setC (c ) ;
        C1 . start ( ) ;
       ThreadC C2 = new ThreadC ( ) ;
        C2 . setName ( "C2" ) ;
        C2 . setC (c ) ;
        C2 . start ( ) ;
        C1 . join ( ) ;
        C2 . join ( ) ;
    }
}


运行结果:

子类B继承了父类A,但是没有重写test方法,ThreadB仍然是同步的。子类C继承了父类A,也重写了test方法,但是未明确写上synchronized,所以这个方法并不是同步方法。只有显式的写上synchronized关键字,才是同步方法。

所以synchronized不能继承这句话有歧义,我们只要记住子类如果想要重写父类的同步方法,synchronized关键字一定要显示写出,否则无效。

修饰静态方法

synchronized 
static 
void 
test
(
)
{

  //具体代码
}


修饰类

 
synchronized 
(
Example2
.class
) 
{

    //具体代码
}

Java对象 Mark Word

在JVM中,对象在内存中的布局分为三块区域:对象头,实例数据和对齐数据,如下图:

其中Mark Word值在 不同锁状态下的展示如下:(重点看线程id,是否为偏向锁,锁标志位信息)

在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是 16个字节。Talk is cheap. Show me the code. 咱来看代码。

  • 我们想要看Java对象的Mark Word,先要加载一个jar包,在pom.xml添加即可。


<dependency
>

<groupId >org .openjdk .jol < /groupId >
<artifactId >jol -core < /artifactId >
<version > 0.9 < /version >
< /dependency >


  • 新建一个对象A,拥有初始值为666的变量x。


public 
class 
A 
{

    private int x = 666 ;
}

  • 新建一个测试类test,这涉及到刚才加载的jar,我们打印Java对象。


import org
.openjdk
.jol
.info
.ClassLayout
;


public class test {
    public static void main ( String [ ] args ) {
        A a = new A ( ) ;
       System .out . println ( ClassLayout . parseInstance (a ) . toPrintable ( ) ) ;
    }
}


  • 我们发现对象头(object header)占了 12个字节,为啥和上面说的16个字节不一样。

  • 其实上是默认开启了指针压缩,我们需要关闭指针压缩,也就是添加

  • -XX:-UseCompressedOops
  • 配置。

  • 再次执行,发现对象头为16个字节。


偏向锁

什么是偏向锁

JDK1.6之前锁为重量级锁(待会说,只要知道他和内核交互,消耗资源),1.6之后Java设计人员发现很多情况下并不存在多个线程竞争的关系,所以为了资源问题引入了 无锁偏向锁轻量级锁重量级锁的概念。先说偏向锁,他是偏心,偏袒的意思,这个锁会偏向于第一个获取他的线程。

偏向锁演示

  • 创建并启动一个线程,run方法里面用了synchronized关键字,功能是打印this的Java对象。


public 
class 
test 
{

    public static void main ( String [ ] args ) {
        Thread thread = new Thread ( new Runnable ( ) {
           @Override
            public void run ( ) {
                synchronized ( this ) {
                   System .out . println (ClassLayout . parseInstance ( this ) . toPrintable ( ) ) ;
                }
            }
        } ) ;
       thread . start ( ) ;
    }
}

标红的地方为000,根据之前Mark Word在不同状态下的标志,得此为无锁状态。理论上一个线程使用synchronized关键字,应为偏向锁。

  • 实际上偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,所以需要添加参数 -XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。

  • 重新运行下代码,发现标红地方101,对比Mark Word在不同状态下的标志,得此状态为偏向锁。

//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!

偏向锁原理图解

  • 在线程的run方法中,刚执行到synchronized,会判断当前对象是否为偏向锁和锁标志,没有任何线程执行该对象,我们可以看到是否为偏向锁为0,锁标志位01,即无锁状态。

  • 线程会将自己的id赋值给markword,即将原来的hashcode值改为线程id,是否是偏向锁改为1,表示线程拥有对象锁,可以执行下面的业务逻辑。 如果synchronized执行完,对象还是偏向锁状态;如果线程结束之后,会撤销偏向锁,将该对象还原成无锁状态。

  • 如果同一个线程中又对该对象进行加锁操作,我们只要对比 对象的线程id是否与 线程id相同,如果相同即为线程锁重入问题。

优势

加锁和解锁不需要额外的消耗,和执行非同步方法相比只有纳秒级的差距。

白话翻译

线程1锁定对象this,他发现对象为无锁状态,所以将线程id赋值给对象的Mark Word字段,表示对象为线程1专用,即使他退出了同步代码,其他线程也不能使用该对象。

同学A去自习教室C,他发现教室无人,所以在门口写了个名字,表示当前教室有人在使用,这样即使他出去吃了饭,其他同学也不能使用这个房间。

轻量锁

什么是轻量级锁

在多线程交替同步代码块的情况下,线程间没有竞争,使用轻量级锁可以避免重量级锁引入的性能消耗。

轻量级图解

  • 在刚才偏向锁的基础上,如果有另外一个线程也想错峰使用该资源,通过对比线程id是否相同,Java内存会立刻撤销偏向锁(需要等待全局安全点),进行锁升级的操作。

  • 撤销完轻量级锁,会在线程1的方法栈中新增一个锁记录,对象的Mark Word与锁记录交换。

优势

线程不竞争的时候,避免直接使用重量级锁,提高了程序的响应速度。

白话翻译

在刚才偏向锁的基础上,另外一个线程也想要获取资源,所以线程1需要撤销偏向锁,升级为轻量锁。

同学A在使用自习教室外面写了自己的名字,所以同学B来也想要使用自习教室,他需要提醒同学A,不能使用偏向锁,同学A将自习教室门口的名字擦掉,换成了一个书包,里面是自己的书籍。这样在同学A不使用自习教室的时候,同学B也能使用自习教室,只需要将自己的书包也挂在外面即可。这样下次来使用的同学就能知道已经有人占用了该教室。

重量级锁

什么是重量级锁

当多线程之间发生竞争,Java内存会申请一个Monitor对象来实现。

重量级锁原理图解

在刚才的轻量级锁的基础上,线程2也想要申请资源,发现锁的标志位为00,即为轻量级锁,所以向内存申请一个Monitor,让对象的MarkWord指向Monitor地址,并将ower指针指向线程1的地址,线程2放在等待队列里面,等线程1指向完毕,释放锁资源。

Monitor源码分析

环境搭建

我们去官网 http://openjdk.java.net/找下open源码,也可以通过其他途径下载。源码是C实现的,可以通过DEV C++工具打开,效果如下图:

构造函数

我们先看下 \hotspot\src\share\vm\runtime\ObjectMonitor.hpp,以.hpp结尾的文件是导入的一些包和一些声明,之后可以被.cpp文件导入。


ObjectMonitor
(
) 
{

   _header       = NULL ;
   _count         = 0 ;
   _waiters       = 0 ,
   _recursions   = 0 ; //线程重入次数
   _object       = NULL ; //存储该monitor的对象
   _owner         = NULL ; //标识拥有该monitor的线程
   _WaitSet       = NULL ; //处于wait状态的线程,会加入到_waitSet
   _WaitSetLock   = 0 ;
   _Responsible   = NULL ;
   _succ         = NULL ;
   _cxq           = NULL ; //多线程竞争锁时的单项列表
   FreeNext       = NULL ;
   _EntryList     = NULL ; //处于等待锁lock状态的线程,会被加入到该列表
   _SpinFreq     = 0 ;
   _SpinClock     = 0 ;
   OwnerIsThread = 0 ;
   _previous_owner_tid = 0 ;
  }

锁竞争的过程

我们先看下 \hotspot\src\share\vm\interpreter\interpreterRuntime.cppIRT_ENTRY_NO_ASYNC即为锁竞争过程。


//%note monitor_1

IRT_ENTRY_NO_ASYNC ( void , InterpreterRuntime : : monitorenter (JavaThread * thread , BasicObjectLock * elem ) )
#ifdef ASSERT
 thread - > last_frame ( ) . interpreter_frame_verify_monitor (elem ) ;
#endif
  if (PrintBiasedLockingStatistics ) {
   Atomic : : inc (BiasedLocking : : slow_path_entry_count_addr ( ) ) ;
  }
 Handle h_obj (thread , elem - > obj ( ) ) ;
  assert (Universe : : heap ( ) - > is_in_reserved_or_null ( h_obj ( ) ) ,
        "must be NULL or an object" ) ;
//是否使用偏向锁,可加参数进行设置  if (UseBiasedLocking) { //如果可以使用偏向锁,即进入fast_enter
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
   ObjectSynchronizer : : fast_enter (h_obj , elem - > lock ( ) , true , CHECK ) ;
  } else { //如果不可以使用偏向锁,即进行slow_enter
   ObjectSynchronizer : : slow_enter (h_obj , elem - > lock ( ) , CHECK ) ;
  }
  assert (Universe : : heap ( ) - > is_in_reserved_or_null (elem - > obj ( ) ) ,
        "must be NULL or an object" ) ;
#ifdef ASSERT
 thread - > last_frame ( ) . interpreter_frame_verify_monitor (elem ) ;
#endif

slow_enter实际上调用的ObjectMonitor.cpp的enter 方法


void 
ATTR ObjectMonitor
:
:
enter
(

TRAPS
) 
{

  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
 Thread * const Self = THREAD ;
  void * cur ;

  //通过CAS操作尝试将monitor的_owner设置为当前线程
 cur = Atomic : : cmpxchg_ptr (Self , &_owner , NULL ) ;
  //如果设置不成功,直接返回
  if ( cur == NULL ) {
    // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
    assert (_recursions == 0   , "invariant" ) ;
    assert (_owner       == Self , "invariant" ) ;
    // CONSIDER: set or assert OwnerIsThread == 1
    return ;
  }
  //如果_owner等于当前线程,重入数_recursions加1,直接返回
  if ( cur == Self ) {
    // TODO-FIXME: check for integer overflow!  BUGID 6557169.
    _recursions ++ ;
    return ;
  }

  //如果当前线程第一次进入该monitor,设置重入数_recursions为1,_owner为当前线程,返回
  if (Self - > is_lock_owned ( (address )cur ) ) {
  assert (_recursions == 0 , "internal state error" ) ;
   _recursions = 1 ;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
   _owner = Self ;
   OwnerIsThread = 1 ;
    return ;
  }

//如果未抢到锁,则进行自旋优化,如果还未获取锁,则放入到list里面
  // We've encountered genuine contention.
assert (Self - >_Stalled == 0 , "invariant" ) ;
 Self - >_Stalled = intptr_t ( this ) ;

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  if ( Knob_SpinEarly && TrySpin (Self ) > 0 ) {
    assert (_owner == Self       , "invariant" ) ;
    assert (_recursions == 0     , "invariant" ) ;
    assert ( ( (oop ) ( object ( ) ) ) - > mark ( ) == markOopDesc : : encode ( this ) , "invariant" ) ;
    Self - >_Stalled = 0 ;
    return ;
  }

assert (_owner != Self           , "invariant" ) ;
assert (_succ   != Self           , "invariant" ) ;
assert (Self - > is_Java_thread ( )   , "invariant" ) ;
 JavaThread * jt = (JavaThread * ) Self ;
assert ( !SafepointSynchronize : : is_at_safepoint ( ) , "invariant" ) ;
assert (jt - > thread_state ( ) != _thread_blocked   , "invariant" ) ;
assert ( this - > object ( ) != NULL   , "invariant" ) ;
assert (_count >= 0 , "invariant" ) ;

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
 Atomic : : inc_ptr ( &_count ) ;

 EventJavaMonitorEnter event ;

  { // Change java thread status to indicate blocked on monitor enter.
   JavaThreadBlockedOnMonitorEnterState jtbmes (jt , this ) ;

    DTRACE_MONITOR_PROBE (contended__enter , this , object ( ) , jt ) ;
    if ( JvmtiExport : : should_post_monitor_contended_enter ( ) ) {
     JvmtiExport : : post_monitor_contended_enter (jt , this ) ;
    }

   OSThreadContendState osts (Self - > osthread ( ) ) ;
   ThreadBlockInVM tbivm (jt ) ;

   Self - > set_current_pending_monitor ( this ) ;

    // TODO-FIXME: change the following for(;;) loop to straight-line code.
    for ( ; ; ) {
     jt - > set_suspend_equivalent ( ) ;
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()

    EnterI ( THREAD ) ;

      if ( ! ExitSuspendEquivalent (jt ) ) break ;

      //
      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
         _recursions = 0 ;
     _succ = NULL ;
    exit ( false , Self ) ;

     jt - > java_suspend_self ( ) ;
    }
   Self - > set_current_pending_monitor ( NULL ) ;
  }

 Atomic : : dec_ptr ( &_count ) ;
assert (_count >= 0 , "invariant" ) ;
 Self - >_Stalled = 0 ;

  // Must either set _recursions = 0 or ASSERT _recursions == 0.
assert (_recursions == 0     , "invariant" ) ;
assert (_owner == Self       , "invariant" ) ;
assert (_succ   != Self       , "invariant" ) ;
assert ( ( (oop ) ( object ( ) ) ) - > mark ( ) == markOopDesc : : encode ( this ) , "invariant" ) ;

  // The thread -- now the owner -- is back in vm mode.
  // Report the glorious news via TI,DTrace and jvmstat.
  // The probe effect is non-trivial.  All the reportage occurs
  // while we hold the monitor, increasing the length of the critical
  // section.  Amdahl's parallel speedup law comes vividly into play.
  //
  // Another option might be to aggregate the events (thread local or
  // per-monitor aggregation) and defer reporting until a more opportune
  // time -- such as next time some thread encounters contention but has
  // yet to acquire the lock.  While spinning that thread could
  // spinning we could increment JVMStat counters, etc.

  DTRACE_MONITOR_PROBE (contended__entered , this , object ( ) , jt ) ;
  if ( JvmtiExport : : should_post_monitor_contended_entered ( ) ) {
   JvmtiExport : : post_monitor_contended_entered (jt , this ) ;
  }
//java学习交流:737251827  进入可领取学习资源及对十年开发经验大佬提问,免费解答!
  if ( event . should_commit ( ) ) {
   event . set_klass ( ( (oop ) this - > object ( ) ) - > klass ( ) ) ;
   event . set_previousOwner ( ( TYPE_JAVALANGTHREAD )_previous_owner_tid ) ;
   event . set_address ( ( TYPE_ADDRESS ) (uintptr_t ) ( this - > object_addr ( ) ) ) ;
   event . commit ( ) ;
  }

  if ( ObjectMonitor : :_sync_ContendedLockAttempts != NULL ) {
    ObjectMonitor : :_sync_ContendedLockAttempts - > inc ( ) ;
  }
}


白话翻译

同学A在使用自习教室的时候,同学B在同一时刻也想使用自习教室,那就发生了竞争关系。所以同学B在A运行过程中,加入等待队列。如果此时同学C也要使用该教室,也会加入等待队列。等同学A使用结束,同学B和C将竞争自习教室。

自旋优化

自旋优化比较简单,如果将其他线程加入等待队列,那之后唤醒并运行线程需要消耗资源,所以设计人员让其空转一会,看看线程能不能一会结束了,这样就不要在加入等待队列。

白话来说,如果同学A在使用自习教室,同学B可以回宿舍,等A使用结束再来,但是B回宿舍再来的过程需要1个小时,而A只要10分钟就结束了。所以B可以先不回宿舍,而是在门口等个10分钟,以防止来回时间的浪费。

结语

唉呀妈呀,终于结束了,累死了。终于将synchronized写完了,如果有不正确的地方,还需要各位指正。如果觉得写得还行,麻烦帮我点赞,评论哈。



请使用浏览器的分享功能分享到微信等