锁:
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();