彻底理解synchronized - omigaw/spring- GitHub Wiki
1.Mark Word存储结构
无锁状态 对象的hashCode 对象分代年龄 是否是偏向锁 锁标志位
轻量级锁
重量级锁
GC标记
偏向锁 线程ID Epoch 对象分代年龄 1 01
2.synchronized使用方法
* 方法
1. 实例方法
锁住的是"类的实例对象"。
public synchronized void method(){
.......
}
2. 静态方法
锁住的是"类对象"。
public static synchronized void method(){
.......
}
* 代码块
1. 实例对象
类的实例对象
synchronized(this){
.......
}
2. class对象
类对象
synchronized(SynchronizedDemo.class){
.......
}
3. 任意实例对象Object
实例对象Object
String lock = "";
synchronized(lock){
.......
}
synchronized可以用在方法上也可以使用在代码块中,其中方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种。需要注意的是:`如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。`
2.1 对象锁机制
执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。使用Synchronized进行同步,其关键就是必须对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。
`Synchronized先天具有重入性,每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。`
3.锁优化
锁优化过程中,有两个点需要关注:(1).CAS操作(2).Java对象头
3.1 CAS操作
CAS操作是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就`不会出现阻塞停顿的状态`。
`元老级的Synchronized(未优化前)最主要的问题是:存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。`
在J.U.C包中利用CAS实现类有很多,可以说是支撑起整个concurrency包的实现。
3.1.1 CAS的问题
* ABA问题 :添加版本号
* 自旋时间过长 :使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是有很大的消耗的。
* 只能保证一个共享变量的原子操作 :当对一个共享变量执行操作时CAS能保证其原子性,如果对多个共享变量进行操作,CAS就不能保证其原子性。有一个解决方案是利用对象整合多个共享变量,即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。
3.1.2 Java对象头
在同步的时候是获取对象的monitor,即获取到对象的锁。这个锁就是存在Java对象的对象头。Java对象头里的Mark Word里默认存放的对象的Hashcode,分代年龄和锁标记位。
锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种`锁升级却不能降级`的策略,目的是为了提高获得锁和释放锁的效率。
- 偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1;如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
如何关闭偏向锁
偏向锁在Java 6 和Java 7里默认是开启的,但是它在应用程序启动几秒钟后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。
- 轻量级锁
CAS操作