Synchronized原语
synchronized作为JAVA同步的关键原语,其作用范围可以分为三类:
// sync 原语 作用范围
publicclass SynchronizedSample {
privatefinal Object lock = new Object();
privatestaticint money = 0;
//非静态方法
public synchronized void noStaticMethod(){
money++;
}
//静态方法
public static synchronized void staticMethod(){
money++;
}
public void codeBlock(){
//代码块
synchronized (lock){
money++;
}
}
}
作用范围 | 锁对象 |
---|---|
非静态方法 | 当前对象 => this |
静态方法 | 类对象 => SynchronizedSample.class (一切皆对象,这个是类对象) |
代码块 | 指定对象 => lock (以上面的代码为例 |
在JDK1.6之前synchronized
属于重量级锁,主要依赖os的mutex lock实现,涉及到线程从用户态到内核态的切换,切换开销较大。在1.6后,引入了偏向锁和轻量级锁,提升了synchronized
的性能。
预备知识
JAVA的对象结构主要如下。
其中对象头分为:
对象头结构 | 存储信息-说明 |
---|---|
Mard Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
Klass Word | 存储指向对象所属类(元数据)的指针,JVM通过这个确定这个对象属于哪个类 |
除此之,每个对象都有一个关联的monitor对象,其属性如下:
//👇图详细介绍重要变量的作用
ObjectMonitor() {
_header = NULL;
_count = 0; // 重入次数
_waiters = 0, // 等待线程数
_recursions = 0;
_object = NULL;
_owner = NULL; // 当前持有锁的线程
_WaitSet = NULL; // 调用了 wait 方法的线程被阻塞 放置在这里
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 等待锁 处于block的线程 有资格成为候选资源的线程
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
对于ObjectMonitor对象内部有一个竞争锁的机制,其原理如下:
当有两个线程A,B同时访问synchronized锁时,A抢先拿到了锁对象,这时monitor会把_owner
设置为A,将对象的markword设置为Monitor,锁标记为10。
随后B会被加入waiting queue队列中被阻塞。
JVM每个冲Waiting Queue取一个元素到OnDesk作为候选者。为了防止Waiting Queue被大量线程执行CAS,单独设置了EntryList作为有资格进入OnDesk。而OnDesk作为竞争锁资源的线程,最多只能有一个。处于waiting queue和blocking queue的线程均处于阻塞状态。这种synchronize的锁是非公平的。
Synchronized优化
在1.6后java引入了偏向锁和轻量级锁。通过这些措施来优化了synchronized的加锁开销。