15-synchronized保证线程安全的原理(jvm层面)

本讲我们继续来了解synchronized的原理,上节我们已经从synchronized所实现的同步的外面来了解了synchronized的原理,那么,本讲就来从深层次的层面来看一下synchronized的原理,也就是synchronized底层的实现,当然了也不是非常的偏向底层,更底层的可能就是关于Java虚拟机的实现以及它所调用操作系统的资源,那么,我们不去研究那么深,我们主要是来看一下,从字节码这一块,首先我们还是来看我们之前所用的那个例子,这次看例子我们不再从语法层面看了,而是从字节码层面,在Java虚拟机规范中,可以看到,synchronized在Java虚拟机中的实现原理,就是说Java虚拟机在执行同步代码块的时候,其实它是基于进入和退出monitor对象来实现方法同步的,也就是说,它有两个字节码指令分别就是monitorentermonitorexit,当进入代码块的时候monitorente,当代码块执行完毕之后就会执行monitorexit

那么,我们来看一下是不是这样的,我们如何来看字节码指令呢?之前已经给大家演示过了

我们并不去关注这些字节码指令。任何一个Java的技术研究深了,都和虚拟机是脱不了关系的。所以,我们在了解并发的时候,其实是需要大量的了解Java虚拟机的底层原理,当然了,Java虚拟机在讲解的过程中,其实也应该去涉及并发相关的内容。

其实主要关注的,第17行就是monitorexit,这是退出,第4行就是monitorenter,这是进入,从第四行开始进入执行,执行到第17行退出就可以了,那么,为什么后面还会有monitorexit呢?其实,我们在执行的过程中,它并非是一行一行的去执行的,比如说有异常

比如说我们来看一下这里的异常表,它有可能就直接跳转到第23行,我们发现第23行是aload_1,然后接着执行第24行的monitorexit,就执行了退出,后面还有athrow,就是说

字节码并非是从第一行开始往下挨个的执行,比如说遇到goto了,它可能就会跳,比如说遇到ifle这样的它也会跳,本例中遇到ifle后就跳转到第19行,所以,我们发现ireturn上面有一个monitorexit退出,也就是说,你是不需要担心同步代码块线程拿到锁之后没释放就走了,这在理论层面上是不会发生的,因为,不管是异常退出或者是正常执行或者是扔出异常或者是等等的各种原因,锁都是会释放的。

上面是同步代码块,下面我们看一下同步方法

发现,同步方法中并没有我们想要的monitorenter和monitorexit,方法的同步是使用另外一种方式实现的,但是这种方式在Java虚拟机中并没有给出详细的说明,所以,我们这里也就不去深究它了。好了,这是我们从Java字节码的角度来看synchronized的执行,其实,就是通过monitorenter和monitorexit。

下面我们再来说一个非常重要的问题,我们上一节已经说了,任何一个对象都可以作为同步的锁,也就是所谓的内置锁,为什么它能够作为内置锁呢?也就是锁是存在哪里的呢?

任何对象都可以作为锁,那么,锁信息存在对象的什么地方呢?存在对象头中。那么,对象头又是一个什么样的结构呢?

这么说吧,就是说,我们说对象,我们一般这么说,数组和非数组,我们说我们的普通的对象,比如说一个Object对象,Java虚拟机会开辟两个字宽的存储空间来存储对象头,那么,这个对象头中都有哪些信息呢?这里我们还是不去详细的说,首先,对象头中有一个Mark Word字段,然后,还有Class Metadata Address,这个是干什么的呢?我们说的是对象,并不是仅仅说跟线程相关的,就是说这个对象,那么这个对象它肯定是有一个类型的,它需要指向类型的地址。这是我们普通对象的头信息

那么,如果作为数组,它也是一个对象,我们说万物皆对象,数组也是创建出来的一个对象,那么,这个数组对象,它比我们的普通对象它还多一个信息,Array Length,数组的长度,

那么,这个地方关于锁相关的信息,我们所关注的地方在哪里呢?我们所关注的地方就在Mark Word里面,这个Mark Word里面存的是什么呢?它存的就是对象的一些哈希值啊,包括锁信息等等的一些信息。虚拟机团队为了提高空间的利用率,他定义了几个锁的标志位,根据不同的锁标志位存储不同的状态信息,也就是说,Mark Word这一块空间的利用率是非常高的。

下面我们就开始说关于锁,有几种锁呢?在我们的JDK6之前,synchronized就是一个重量级锁,什么叫重量级锁呢?就是它锁住了,这个线程进去了,其他的线程必须在外面等,但是,到了JDK1.6以后,就不再是这样了,引入了几个性能比较高的概念,第一个叫做偏向锁,就是比较偏向某一个线程,第二个是轻量级锁,第三个就是重量级锁。

首先说第一个偏向锁,什么叫偏向锁呢?先说场景,我们说,锁每次获取锁和释放锁是比较费时间的,会浪费资源,性能不高。而并发的团队发现有很多的情景下,竞争锁不是由多个线程,而是由一个线程在使用,那么,在这种情况下,我们发现,基本上就是,相当于是单线程在访问,那么,每次还是像多线程一样去申请锁,再去获取锁,再去释放锁,这样会浪费很多的资源,于是就有了偏向锁,比如说,我第一个线程过来了,比如这是我们的代码

那么,第一个线程来,要走对象头

直接就查一下,看是不是偏向锁,偏向锁的Mark Word字段还会记录一个东西,叫做线程的id,第二个叫Epoch,第三个叫做对象的分代年龄信息,第四个就是是否是偏向锁,第五个就是锁标志位

这是我们的当偏向锁状态的时候,Mark Word的状态,那么,这里其实跟我们相关的其实就这两个,线程id和锁标志位,当线程进来之后,就会检查它的锁标志位是不是为偏向锁,然后接着会检查这个线程的id,检查线程的id,如果和我们当前的线程的id一致的话,那就不用再获取了(如果是第一次,那么是需要获取的),直接就进去,然后第二次再来的时候,其实它是不释放的,它一直在运行着,它不释放锁,那么,第二次进来的时候,接着再来运行这个同步代码块就可以了,那么就没有了锁的获取和锁的释放过程了,但是,锁毕竟不是单个线程在运行,肯定是在多个线程的情况下运行,那么,偏向锁获取了之后,我们说它一直没有进行撤销,那么,这个偏向锁该如何撤销呢?这个偏向锁使用了一种等到竞争出现才释放锁的机制,所以,当其他的线程尝试竞争偏向锁的时候,那么,持有偏向锁的线程才会去释放偏向锁,这就是关于偏向锁,

那么,我们发现了,这个偏向锁适用于哪种场景呀?它适用的场景其实是非常清晰的,就是说,只有一个线程访问同步代码块,那么使用偏向锁会大大的提高性能,

这个感觉似乎是性能提高的并不多,因为毕竟我们在多线程下访问,但其实是很多情况下我们虽然是写的多线程应用,但是很多方法都是在只有单个线程在访问的情况,当然了,这个问题也是非常突出的,比如说我们的应用确实是,这个方法就是多个线程在同时的来访问,那么,这个情况下我们发现这个问题其实并不会提高性能,反而会降低性能,这是第一个,偏向锁。

第二个叫做轻量级锁,什么叫轻量级锁呢?这个轻量级锁就比较强大了,就是说什么意思呢它可以同时让我们的多个线程进入我们的同步代码块中,也就是说,同时都能够获取锁,那么,这个是怎么做到的呢?首先说轻量级锁是如何加锁的,在线程执行同步代码块之前,Java虚拟机会先在当前线程的栈帧中创建用于存储锁记录的空间,这里面又提到了一个问题,就是说,关于栈帧,栈帧是个什么东西呢?我们知道,Java虚拟机内存区域的划分分为线程私有的和线程共享的两块区域,其中有一块区域叫做虚拟机栈,这一块区域是被线程所独有的,那么,它也就是说,相当于运行时的动态区域,虚拟机栈里面存储的是一个一个的栈帧,那么,当每个方法执行都会创建一个栈帧,这个栈帧中就存储了方法执行的信息,这里不太详细的讲,你只要知道每一个方法的执行都会伴随着栈帧的进栈和出栈就可以了,而栈帧中保存的就是方法的执行信息。Java虚拟机会把当前线程的栈帧中创建用于存储锁记录的空间,就是说,在我们的栈帧中存储锁记录的空间,然后,并将这个对象头中的Mark Word复制到锁记录中,然后开始竞争锁就可以,当竞争成功之后,Mark Word就改变了,Mark Word就会把锁标志位改成轻量级锁,然后接着开始执行同步体,然后,比如说多个线程一块访问,那么,一个线程获得了轻量级锁,那么,另外一个线程肯定是需要等待,它也想去执行,那么,它也去复制Mark Word到虚拟机栈中,接着它要修改这个Mark Word,它发现其实这个Mark Word已经被别的线程获得了锁,所以,它修改不成功,于是,它就不停的去获得不停的去执行这么一段,就是不停的去修改,然后不停的失败,不停的修改不停的失败,直到第一个线程把这个锁释放了,于是它才可以或得到锁,刚才那一个过程就是所谓的自选锁,自选锁我们后面还会讲。就是说,第一个线程执行完毕之后,那么,第二个线程获取到之后,第二个线程就会把锁升级,升级为重量级锁,那么,线程也就会阻塞,当第一个线程执行完毕,并且释放了锁,并且要唤醒第二个线程,接着第二线程开始继续执行,这是关于轻量级锁的执行过程。轻量级锁里面用到了自旋,轻量级锁有什么呢?非常的显然,多个线程可以同时

这就是关于轻量级锁。

当自旋获取锁失败之后,那么,它就会升级为重量级锁,那么,重量级锁就是我们普遍意义上所理解的synchronized(当一个锁进来之后,其他的锁必须在外面等待,没有其他的情况,只有等它执行完毕之后,其他的锁才能够进入。)。

这就是关于synchronized,关于synchronized的原理我们就说到这里。

猜你喜欢

转载自blog.csdn.net/G_66_hero/article/details/85706626