参考资料:https://www.cnblogs.com/myseries/p/12213997.html
synchronized三种用法
- 修饰实例方法,对当前实例对象this加锁
- 修饰静态方法,对当前类的Class对象加锁
- 修饰代码块,指定加锁对象,对给定对象加锁
代码实例:
public class SynchronizedDemo {//修饰实例方法:锁的是当前实例对象public synchronized void test() {}//修饰静态方法,锁的是当前类的Class对象public static synchronized void test2() {}public void test3() {//修饰代码块,锁定指定对象synchronized (this) {}//修饰代码块,锁定指定对象synchronized (SynchronizedDemo.class) {}}}
synchronized实现原理
Java对象的组成:
- 对象头
- 标记字段:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化
- 类型指针。对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 实例数据:存放类的数据信息,父类的信息
- 填充字节:由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。

synchronized修饰代码块和方法的实现原理:
- synchronized修饰代码块的实现:进入同步代码块,先执行monitorenter指令,退出同步代码块,执行monitorexit指令,有两个monitorexit指令,一个是正常退出执行的,另一个是异常退出时执行的。
- synchronized修饰同步方法的实现:执行方法前,会先判断否有标志位ACC_SYNCHRONIZED,该标记位会隐式去调用monitorenter和monitorexit这两个指令。
synchronized锁升级
在JDK1.6之前synchronized锁是重量级锁,性能很差,因为为操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
JSK1.6之后引入了偏向锁和轻量级锁,对从JVM层面对synchronized较大优化
tips:锁升级过程不可逆。
偏向锁:
大部分情况下只有一个线程进行同步调用,此时就没必要进行加锁,这个锁会偏向第一个获得它的线程,该线程会利用CAS操作,将线程ID插入对象头标记字段中,同时修改偏向锁的标记位。然后再加下来的操作中,会根据调用的线程ID进行对比,如果是第一个线程,则不需要进行加锁,直接执行方法即可,不再需要去进行加锁或者解锁操作,如果线程ID不一致,此时就存在了竞争。此时可能就要根据另外线程的情况,可能是重新偏向,也有可能是做偏向撤销,但大部分情况下就是升级成轻量级锁了。
轻量级锁:
偏向锁失效之后就需要进行锁撤销,锁撤销之后就升级为轻量级锁。
轻量级锁主要有两种:
- 自旋锁。就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。但是这个过程会消耗CPU,所以适合同步代码块执行的很快的场景
为了解决空循环消耗cpu的问题,必须要给线程空循环设置一个次数,当线程超过了这个次数,我们就认为,继续使用自旋锁就不适合了,此时锁会再次膨胀,升级为重量级锁。
- 自适应自旋锁。线程空循环等待的自旋次数并非是固定的,而是会动态着根据实际情况来改变自旋等待的次数。
几种锁的优缺点:
错误的加锁方式:
- synchronized(newObject()) 每次调用的创建的都是不同的锁, 相当于无锁
String,Boolean在实现了都用了享元模式,即值在一定范围内,对象是同一个。所以看似是用了不同的对象,其实用的是同一个对象。会导致一个锁被多个地方使用。
private Integer count;public synchronized void test4() {synchronized (count) {}}
正确的加锁方式:
//普通对象锁private final Object lock = new Object();//静态对象锁private static final Object staticLock = new Object();public synchronized void test5() {synchronized (lock) {}synchronized (staticLock) {}}
