java中的锁demo_Java 中关于锁的一些理解

jdk 6 对锁进行了优化,让他看起来不再那么笨重,synchronized有三种形式:偏向锁,轻量级锁,重量级锁.

介绍三种锁之前,引入几个接下来会出现的概念

mark work:

对象头,对象头中存储了一些对象的信息,这个是锁的根本,任何锁都需要依赖mark word 来维持锁的运作,对象头中存储了当前持有锁的线程,hashCode,GC的一些信息都存储在对象头中.

在JVM中,对象在内存中除了本身的数据外还会有个对象头,对于普通对象而言,其对象头中有两类信息:mark word和类型指针。另外对于数组而言还会有一份记录数组长度的数据.

类型指针是指向该对象所属类对象的指针,mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。为了能在有限的空间里存储下更多的数据,其存储格式是不固定的,在32位系统上各状态的格式如下:

77360af2ca3b4b2851d5f762e4739e08.png

可以看到锁信息也是存在于对象的mark word中的。当对象状态为偏向锁时,mark word存储的是偏向的线程ID;当状态为轻量级锁时,mark word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁时,为指向堆中的monitor对象的指针.

Lock Record:

前面对象头中提到了Lock Record,接下来说下Lock Record,Lock Record存在于线程栈中,翻译过来就是锁记录,它会拷贝一份对象头中的mark word信息到自己的线程栈中去,这个拷贝的mark word 称为Displaced Mark Word ,另外还有一个指针指向对象

monitor:

monitor存在于堆中,什么是Monitor?我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。

与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下:

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL

EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程

RcThis:表示blocked或waiting在该monitor record上的所有线程的个数

Nest:用来实现重入锁的计数

HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)

Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁

(摘自:Java中synchronized的实现原理与应用)

说完几个关键概念之后来说一下锁的问题:

偏向锁

偏向锁是锁的级别中最低的锁,举个例子: 在此demo中,获得操作list的一直都是main线程,没有第二个线程参与操作,此时的锁就是偏向锁,偏向锁很轻,jdk 1.6默认开启,当第一个线程进入的时候,对象头中的threadid为0,表示未偏向任何线程,也叫做匿名偏向量

public class SyncDemo1 {

public static void main(String[] args) {

SyncDemo1 syncDemo1 = new SyncDemo1();

for (int i = 0; i < 100; i++) {

syncDemo1.addString("test:" + i);

}

}

private List list = new ArrayList<>();

public synchronized void addString(String s) {

list.add(s);

}

}

当第一个线程进入的时候发现是匿名偏向状态,则会用cas指令把mark words中的threadid替换为当前线程的id如果替换成功,则证明成功拿到锁,失败则锁膨胀;

当线程第二次进入同步块时,如果发现线程id和对象头中的偏向线程id一致,则经过一些比较之后,在当前线程栈的lock record中添加一个空的Displaced Mark Word,由于操作的是私有线程栈,所以不需要cas操作,synchronized带来的开销基本可以忽略;

当其他线程进入同步块中时,发现偏向线程不是当前线程,则进入到撤销偏向锁的逻辑,当达到全局安全点时,锁开始膨胀为轻量级锁,原来的线程仍然持有锁,如果发现偏向线程挂了,那么就把对象的头改为无锁状态,锁膨胀

轻量锁

当锁膨胀为轻量级锁时,首先判断是否有线程持有锁(判断mark work),如果是,则在当前线程栈中创建一个lock record 复制mark word 并且cas的把当前线程栈的lock record 的地址放到对象头中,如果成功,则说明获取到轻量级锁,如果失败,则说明锁已经被占用了,此时记录线程的重入次数(把lock record 的mark word 设置为null),锁会自旋可以进行自适应性自旋,确保在竞争不激烈的情况下仍然可以不膨胀为重量级锁从而减少消耗,如果cas失败,则说明线程出现竞争,需要膨胀为重量级的锁,代码如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

markOop mark = obj->mark();

assert(!mark->has_bias_pattern(), "should not see bias pattern here");

// 如果是无锁状态

if (mark->is_neutral()) {

//设置Displaced Mark Word并替换对象头的mark word

lock->set_displaced_header(mark);

if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {

TEVENT (slow_enter: release stacklock) ;

return ;

}

} else

if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {

assert(lock != mark->locker(), "must not re-lock the same lock");

assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");

// 如果是重入,则设置Displaced Mark Word为null

lock->set_displaced_header(NULL);

return;

}

...

// 走到这一步说明已经是存在多个线程竞争锁了 需要膨胀为重量级锁

lock->set_displaced_header(markOopDesc::unused_mark());

ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

}

重量锁

重量级锁就是我们传统意义上的锁了,当线程发生竞争,锁膨胀为重量级锁,对象的mark word 指向堆中的 monitor,此时会将线程封装为一个objectwaiter对象插入到monitor中的contextList中去,然后暂停当前线程,当持有锁的线程释放线程之前,会把contextList里面的所有线程对象插入到EntryList中去,会从EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫"假定"继承人的原因)。

如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

cd41fdba168b1361a408283cd99872dc.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值