博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
同步(二) - 锁
阅读量:5100 次
发布时间:2019-06-13

本文共 2552 字,大约阅读时间需要 8 分钟。

锁:

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

你可能感兴趣的文章
修改DNS服务器。
查看>>
SCP命令
查看>>
使用Flash Builder建SDK为3系列的项目默认为中文的修改方法
查看>>
SQL优化
查看>>
Luogu P1463 [HAOI2007]反素数ant:数学 + dfs【反素数】
查看>>
C#中建立treeView
查看>>
hadoop的安装和配置
查看>>
spinnaker
查看>>
hdu 1599 find the mincost route(无向图的最小环)
查看>>
转载:解读CSS布局之-水平垂直居中(2)
查看>>
第十八章 30限制数组越界
查看>>
浅谈unique列上插入重复值的MySQL解决方案
查看>>
hdu 4859(思路题)
查看>>
11.2.0.4 sql*loader/oci direct load导致kpodplck wait before retrying ORA-54
查看>>
sql server 2008空间释放
查看>>
团队-科学计算器-最终总结
查看>>
树的遍历 TJUACM 3988 Password
查看>>
UVA 725 - Division
查看>>
bzoj1798: [Ahoi2009]Seq 维护序列seq(线段树)
查看>>
day5
查看>>