深入浅出synchronized关键字
synchronized的作用
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
总结: 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
synchronized的几种使用方法
-
对象锁
-
修饰静态方法
-
-
类锁
-
修饰普通方法
-
修饰代码块
-
使用synchronized会碰到的几种情况
-
多个线程同一时刻访问同一对象的同一普通同步方法:
此时线程之间时串行的关系
-
多个线程同一时刻访问不同对象的同一普通同步方法:
此时线程之间时并行的关系
-
同步方法执行过程中发生异常,同时会释放该锁对象
synchronized如何实现线程同步
反编译如下代码:javap -c ***.class
public class SyncDemo{
int i;
public void reduce(){
synchronized(this){
i++;}
}
}

我们可以看到,在反编译后的代码之中出现了monitorenter和monitorexit两个指令。所以说synchronized的实现实际上就是对monitor对象的争夺。想要弄清楚monitor是如何实现的,我们先来看一段C++源码:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
在上面的源码我们可以看到中有几个关键属性:
-
_owner:指向持有ObjectMonitor对象的线程
-
_WaitSet:存放处于wait状态的线程队列
-
_EntryList:存放处于等待锁block状态的线程队列
-
_recursions:锁的重入次数 【从这个不难看出synchronized是可重入锁吧】
-
_count:用来记录该线程获取锁的次数
所以,总结一下:当一个线程需要访问受保护的数据(即需要获取对象的Monitor)时
-
首先在EntryList中进行排序等待(当然,这里不是真正意义上的排序,因为monitor对象的获取不是公平的)
-
如果当前没有线程占有monitor对象,则该线程会和其他EntryList中的线程去抢占monitor对象
-
抢占成功,_count 加一,开始执行相关方法,若执行过程中调用了wait方法,则该线程会放弃获得到的monitor对象,并加入WaitSet中,等待notify/notifyall方法唤醒。
注意:被唤醒的线程并不会立即获取到monitor对象,只回去重新加入EntryList与其他线程争夺锁对象
上面只说了synchronized修饰代码块时是如何实现的,其实当synchronized修饰方法时,实际上就是添加了一个标识符来表示此方法时同步方法,具体的实现方法和上面差不多。
有细心的小伙伴会发现上面的反编译代码之中出现了两个monitorexit关键字,这是为什么呢??
上面我们说到,同步方法执行过程中发生异常,同时会释放该锁对象,实际上就是因为这个关键字,作用是为了在执行过程中遇到异常能够及时释放锁对象
synchronized锁膨胀【锁升级】
这里我们先来讲一下在jvm队中对象的组成
-
对象头
-
运行时数据
-
哈希码
-
GC分代年龄
-
MarkWord【锁状态标志】
![]()
-
-
-
数组长度【只有数组才会有】
-
-
实例数据
程序代码中所定义的各种类型的字段
-
对其填充
由于HotSpot的自动内存管理要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,对象头的数据正好是8的整数倍,所以当实例数据不够8字节整数倍时,需要通过对齐填充进行补全。
synchronized锁升级主要是通过对象头中的markword来实现的:
无锁 >> 偏向锁 >> 轻量级锁 >> 重量级锁
-
-
-


浙公网安备 33010602011771号