java synchronized的基本原理

1、在多线程并发编中synchronized一直是元老级角色,很 多人都会称呼它为重量级锁。但是,随着 Java SE 1.6对 synchronized synchronized 进行了各种优化之后,有些情况下它就并不那么重Java SE 1.6中为了减少获得锁和释放带来的性能消耗而引入的偏向锁和轻量级锁。

synchronized的基本语法
synchronized 有三种方式来加锁,分别是
1、修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁(不同对象可以同时执行加锁部分的代码)
2、静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁(跨对象的锁,类级别的锁,多个对象同时只能有一个对象能访问静态方法)
3、修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁(不同对象可以同时执行加锁(类锁除外)部分的代码)。
不同的修饰类型,代表锁的控制粒度不同。

1、那么锁是是通过什么表现出来的呢,有锁是什么状态、无锁是什么状态?

2、这个状态需要对多个线程共享。

 

在HotSpot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Header)、实例数据(Instacne Data)、对齐填充(Padding)

下图为网络来源

 

java synchronized的基本原理_第1张图片

对象头(Header):
markword:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit。
klass :对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。 
数组长度(只有数组对象有) :如果对象是一个数组,那在对象头中还必须有一块数据用于记录数组长度。
因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
实例数据(Instance Data):
是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
 这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。HotSpot虚拟机 默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。(可看https://blog.csdn.net/zqz_zqz/article/details/70246212)
对齐填充(Padding):
齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。
由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
以上内容转载自http://blog.csdn.net/lihuifeng/article/details/51681146

 

java synchronized的基本原理_第2张图片

java synchronized的基本原理_第3张图片

 

 

1、为什么每个对象都可以实现锁

a、java中的任何对象都派生自Object类,而每个java Object在JVM内部有一个native的C++对象opp/oopDesc进行对应

b、

java synchronized的基本原理_第4张图片

 

3、synchronized锁的升级

锁一共有4种状态,从低到高分别是:无锁状态、偏向锁、轻量级锁、重量级锁,这几个状态会随着竞争逐渐升级但是不能降级。

3.1偏向锁

HotSpot[1]的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同
一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并
获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出
同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否
存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需
要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则
使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

(1)偏向锁的撤销
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,
持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正
在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,
如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈
会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他
线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。下图中的线
程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。

java synchronized的基本原理_第5张图片

 

(2)关闭偏向锁
偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如
有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程
序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-
UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

3.2轻量级锁

(1)轻量级锁加锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并
将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用
CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失
败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
(2)轻量级锁解锁
轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成
功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是
两个线程同时争夺锁,导致锁膨胀的流程图。

java synchronized的基本原理_第6张图片

3.4重量级锁

当轻量级锁升级为重量级锁后只能被挂起阻塞通过来等待唤醒了。

java synchronized的基本原理_第7张图片

java synchronized的基本原理_第8张图片

 

java synchronized的基本原理_第9张图片

你可能感兴趣的:(concurrency)