一.锁
- synchronized:依赖JVM
- Lock:依赖特殊 cpu指令,代码实现,ReentrantLock
二.synchronized-原子性-修饰的对象
- 修饰代码块:大括号括起来的代码,作用于调用对象
- 修饰方法:整个方法,作用于调用对象
- 修饰静态方法:真个静态方法,作用于所有对象
- 修饰类:括号括起来的部分,作用于所有对象
三.synchronized作用:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题
四.synchronized实现原理:
Synchronized用的锁是存在Java对象头里的,synchronized是JVM实现的一种锁,其中锁的获取和释放分别是monitorenter和monitorexit指令,该锁在实现上分为了偏向锁、轻量级锁和重量级锁,其中偏向锁在1.6是默认开启的,轻量级锁在多线程竞争的情况下会膨胀成重量级锁。
- java对象头和Monitor是Synchronized实现锁的基础
Java头对象由Mark Word(标记字段) 和 Class Metadata Address(类型指针) 组成:
虚拟机位数 | 头对象结构 | 说明 |
---|---|---|
32/64bit | Mark Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
32/64bit | Class Metadata Address | 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。 |
五.什么是Monitor?
我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为对象监视器。
六.java1.6之后synchronized的优化
jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
七. 锁主要存在四中状态:
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
八.synchronized实现原理-图解:
枷锁过程概述:
就是在进入加锁代码块的时候加一个monitorenter的指令,然后针对锁对象关联的monitor累加加锁计数器,同时标识自己这个线程加了锁,通过monitor里的加锁计数器可以实现可重入的加锁.
在出锁代码块的时候,加一个monitorexit的指令,然后递减锁计数器,如果锁计数为0,就会标志当前线程不持有锁,释放锁,然后wait和notify关键字的实现也是依托于monitor实现的,有线程执行wait之后,自己会加入一个waitset中等待唤醒获取锁,notifyall操作会从monitor的waitset中唤醒所有的线程,让他们竞争获取锁.
对上面图形进行详解:
MyObject lock = new MyObject();
synchronized(lock) {
}
- java对象都是分为对象头和实例变量两块的,其中实例变量就是大家平时看到的对象里的那些变量数据。然后对象头包含了两块东西,一个是Mark Word(包含hashCode、锁数据、GC数据,等等),另一个是Class Metadata Address(包含了指向类的元数据的指针)
- 在Mark Word里就有一个指针,是指向了这个对象实例关联的monitor的地址,这个monitor是c++实现的,不是java实现的。这个monitor实际上是c++实现的一个ObjectMonitor对象,里面包含了一个_owner指针,指向了持有锁的线程。
- ObjectMonitor里还有一个entrylist,想要加锁的线程全部先进入这个entrylist等待获取机会尝试加锁,实际有机会加锁的线程,就会设置_owner指针指向自己,然后对_count计数器累加1次
- 各个线程尝试竞争进行加锁,此时竞争加锁是在JDK 1.6以后优化成了基于CAS来进行加锁,理解为跟之前的Lock API的加锁机制是类似的,CAS操作,操作_count计数器,比如说将_count值尝试从0变为1
- 如果成功了,那么加锁成功了;如果失败了,那么加锁失败了
- 然后释放锁的时候,先是对_count计数器递减1,如果为0了就会设置_owner为null,不再指向自己,代表自己彻底释放锁
- 如果获取锁的线程执行wait,就会将计数器递减,同时_owner设置为null,然后自己进入waitset中等待唤醒,别人获取了锁执行notify的时候就会唤醒waitset中的线程竞争尝试获取锁