简述:
什么是synchronized?sysnchronized就是同步的意思,故名思意是用来保证线程安全的。
synchronized(同步):当一个或者多个线程同时访问一个临界资源的时候,只允许有一个
线程访问,其他线程排队等候。当这个线程彻底把锁释放之后,才允许下个线程去访问
该临界资源,并且再次获取该锁,执行下面操作,直至把锁释放。下图可以清楚的展示该结
论。
图一:没有加synchronized关键字,当多个线程并发访问该资源的时候,当其中一
个线程去执行count--操作,将值减一,如果第一个线程读到它是0,然会减1,当这
个线程还没有将数据保存到自己的线程栈中,第二个线程又来将他的值又改为0,所以
结果为0。这就说明在访问临界资源的时候,他的值可能被其它线程修改。
图二:
加了同步关键字,synchronized,也就是说多个线程并发访问该临界资源的时候,当
其中一个线程去执行count--操作的时候,读到它是0,然后进行减1,然后进行打印
当第二个线程来访问该资源的时候,首先看第一个线程是否释放该锁,释放该锁,第
二个线程获取该锁,执行操作,也就是说只允许一个线程对它操作,其它线程不允
许修改该操作。
2.介绍完概念型的东西,再来说说它的原理,它是怎么执行的,作用在哪
里?它是给对象头(markword和class pointer)上锁,锁的是对象头。
那么对象在内存中是如何布局的呢?
上图就清晰的展示对象在内存的的关系布局图,那么具体的这几部分都代表什么呢?一一来说下:
1. markword:存储一些存储锁的信息,有偏向锁记录,偏向时间戳,锁标志位,偏向线
程,id,GC分代年龄,hahcode,basied_lock(锁状态)。指针记录。
2.类型指针(class pointer):类元数据指针,当我们new出来一个对象,可以根据
该指针到jvm虚拟机的方法区找到对应的类。就知道对象是哪个类的实例。
3.实例数据(instance):位于虚拟机的堆区,存放实例数据。
4.对齐填充(padding):什么也不存放,由于虚拟机给默认的对象是8的倍数,当不够
8的倍数,会自动填充。
介绍完对象的信息,我们并且知道synchronized上锁的地方是对象头的markword,那么jdk8中的markword的实现是怎样的呢?
总共记录的了4种锁状态,其中GC是垃圾回收器,当啥时候堆内存满了的时候才启用它,一般虚拟机默认不会启用。
无锁态: 在堆内存中开辟25bit的空间来存放对象的hashcode,开辟4bit的空间存
GC分代年龄。1bit空间用来判断是否偏向锁:0,2bit存放锁标志位:01。
轻量级锁:直接开辟30bit的空间来存放指向自身线程栈记录的指针,开辟2bit存放
锁标志位:00
重量级锁:开启30bit的空间来存放互斥量(重量级锁)的指针。开辟2bit存放锁标志
位:10
GC分代年龄:存放的数据为空,开辟2bit空间存放锁标识位:11
偏向锁:开辟23bit空间用来标识自身线程栈的线程ID,2bit存放偏向时间戳(epoch),
4bit存放GC分代年龄,1bit用来标识是否为偏向锁位:1,2bit来标识锁标志位。
这就是jdk8对markword的实现。那么到底是怎么上锁的呢?锁的是对象哪里呢?
上面就是通过jol框架打印出来的对象关系布局图,红色标记为就记录我们的锁状态和锁标志位。这就是锁的对象头。0位无锁状态。
那么synchronized是可以从以下3方面的实现:
1.源码角度:
这就是源码角度的对象上锁。
2.字节码层级的实现:通过monitorenter和monitorexit指令来给对象上锁和解锁,当执行monitorenter的时候给对象加锁,获取monitor对象监视器,进行以下执行操作。当执行完monitorexit释放锁。大家看下图:
上图我们看出从字节码层级上的synchronized的实现。
3.HotSpot的实现(可以查看openjdk的源码,直接从github官网下载):
直接奉上源码的实现:
首先,说明下,在软件层面的最终实现是:lock指令 lock cmpxchg =cas+修改变量值
在硬件层面,lock指令在执行后面指令的时候锁定了一个北桥信号(电信号)。
在c++源码中:有一条汇编指令:asm volatile (LOCK_IF MP(%4) "cmpxchg %1,(%3)")
这行代码是已经接入操作系统了,volatile的底层最终实现也是该lock指令
至此,我们就把synchronized的实现原理以及在哪里上锁弄明白了,这是自己对此的了解,望对大家有帮助。后面我会给大家分享锁竞争与锁升级的过程。