同步(二) - 锁

锁:

Java 中锁的种类分为:偏向锁、自旋锁、轻量级锁、重量级锁

锁的使用方式为:先提供偏向锁,不满足的时候,升级为轻量级锁,如果再不满足的时候,膨胀成重量级锁。自旋锁是一个过渡的状态,不是一种实际的锁类型。锁可以升级不可以降级。

偏向锁

  如果代码中不会存在竞争的关系的时候,为了让线程获得锁的代价更低,首先使用的这种锁。(JVM编译代码,解释执行的时候,会自动的放弃同步信息,消除synchronized的同步代码结果)

  原理

一个线程访问同步块并获取锁时,会在对象头栈帧中的锁记录存储锁偏向的线程ID,以后此线程进入和退出同步块的时候不需要进行CAS操作来加锁解锁(只要检测Mark Word 里面存储的是不是线程的ID)。

如果测试成功,表示已经获得锁了。如果失败,需要测试偏向锁的标记是否是1,如果是0,就尝试通过CAS来竞争锁;如果是1,尝试使用CAS将对象头偏向锁指向当前线程。

偏向锁只有等到竞争的时候才会释放锁。如果其他线程竞争偏向锁,会先暂停拥有偏向锁的线程,检测是否处于活动状态,否:则对象头设置成无锁状态;是:线程的栈会被执行,Mark Word要么重新偏向其他线程,要么恢复到无锁或者标记对象为不合适作为锁的对象。

  PS

可以通过JVM参数关闭偏向锁:--XX UseBiasedLocking = false, 默认直接进入轻量级锁的状态。

自旋锁

  是一个状态,当前线程尝试使用自旋的方式来获得轻量级锁。

轻量级锁

  当偏向锁不满足的时候,也就是有多线程的并发访问,锁定了同一个对象,这时候会先提升为轻量级锁(也是使用标记ACC_SYNCHRONIZED标记记录的。ACC_UNSYNCHRONIZED标记记录未获取到锁信息的线程)。

  原理

执行同步代码块前,会在栈帧中创建用于储存锁记录的空间,然后将对象头的Mark Word 复制到锁记录中(称之为 displaced mark word)。然后线程尝试使用CAS将对象头中Mark Word 替换成指向锁记录的指针,成功了就是获得锁了,失败了就会不断自旋来获得。

解锁的时候,会使用原子的CAS操作来将锁记录替换回到对象头中,就释放锁,如果失败表示当前锁存在竞争(别的线程自旋次数达到限定,就会将锁膨胀成重量级锁),此时已经Mark Word保存的已经是重量级锁的指针,此时竞争锁的线程均被阻塞。然后释放锁,唤醒被阻塞的线程重新争夺锁访问同步块。

重量级锁

  持有重量级锁的线程才能执行该资源的同步方法,其余的线程会被阻塞。Mark Word  指向 monitor(互斥量)的首地址。

  原理

每一个对象都有一个monitor对象与之对应,当一个monitor被持有了之后,它将处于锁定状态。代码块的同步是使用monitorEnter 和 monitorExit指令来实现的。monitorEnter指令在编译后会被插入到同步代码块开始的位置,monitorExit会被插入到代码结束的位置。线程执行到enter的时候会尝试获取monitor的所有权。

monitor的属性:

_Owner标记用于记录当前执行线程。初始时为NULL。当有线程占有该monitor时,owner标记为该线程的唯一标识。当线程释放monitor时,owner又恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全的。

_count 记录进入锁的次数

_WaitSet是用于管理等待队列(wait)线程的,

_EntryList是用于管理锁池阻塞线程的(_cxq队列中有资格的),

_cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq是一个临界资源,JVM通过CAS原子指令来修改_cxq队列。_cxq是一个后进先出的stack(栈)。

ACC_SYNCRHONIZED

当JVM执行引擎执行某一个方法时,其会从方法区中获取该方法的access_flags,检查其是否有ACC_SYNCRHONIZED标识符,若是有该标识符,则说明当前方法是同步方法,需要先获取当前对象的monitor,再来执行方法。

过程

当多线程并发访问同一个同步代码时,首先会进入_EntryList,当线程获取锁标记后,monitor中的_Owner记录此线程,并在monitor中的计数器执行递增计算(+1),代表锁定,其他线程在_EntryList中继续阻塞。 若执行线程调用wait方法,则monitor中的计数器执行赋值为0计算,并将_Owner标记赋值为null,代表放弃锁,执行线程进如_WaitSet中阻塞。 若执行线程调用notify/notifyAll方法,_WaitSet中的线程被唤醒,进入_EntryList中阻塞,等待获取锁标记。 若执行线程的同步代码执行结束,同样会释放锁标记,monitor中的_Owner标记赋值为null,且计数器赋值为0计算。

其他锁

ReentracntLock

公平锁

  公平锁会记录等待时长,等待最久的先执行。Queue。

 private Lock lock = new ReentrantLock(true);

尝试锁

  使用尝试锁,如果无法获得资源,那么将不会阻塞,进而执行其他代码。

  

lock.tryLock();
阻塞尝试锁,阻塞一定时间来尝试获取锁的标记。
lock.tryLock(5, TimeUnit.SECONDS);

 

条件

  Condition, 为Lock增加条件。当条件满足时,做什么事情,如加锁或解锁。如等待或唤醒。

 private Lock lock = new ReentrantLock();
 private Condition producer = lock.newCondition();
 private Condition consumer = lock.newCondition();

producer.await();
consumer.signalAll();

 

 

转载于:https://www.cnblogs.com/RobertLionLin/p/11511570.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值