lock


前戏java里面提供了两种锁机制,基于jvm层面实现的关键字synchronized和基于jdk层面实现的Lock锁。


synchronized关键字

基本使用:synchronized关键字可以用来修饰静态方法,普通方法,代码块,获取到的锁对象分别为当前类Class对象,当前实例对象和synchronized括号里面的对象。

加锁原理:synchronized加锁是基于对象监视器Monitor的monitorenter和monitorexit。在java中,每个对象都会有一个对象监视器,当synchronized修饰代码块时,在开始位置会加上monitorenter指令,在方法结束和异常处会插入monitorexit指令。当执行monitorentrt指令时,会去获取锁的Monitor对象,若是获取到,执行,获取不到的话,线程阻塞。

synchronized的锁升级

偏向锁:当线程执行时,会去修改对象的对象头(Mark Word)中线程id,若是修改成功,执行。若是修改不成功,需要将偏向锁升级为轻量级锁。锁升级的过程是,该获取锁的线程通知Mark Word中标识的线程,使其进入暂停状态。

轻量级锁:争抢锁的线程会去将对象的对象头(Mark Word)拷贝的线程栈中,并将对象头指向该栈(cas操作),若是执行成功,获取到锁,执行代码。若是执行失败,自旋等待其它线程释放锁。

重量级锁:当自旋超过了一定的时间之后,若是还不能获取到锁,将会升级为重量级锁,线程阻塞。


Lock接口

特性 描述
尝试非阻塞的获取锁 当前线程尝试获取锁,若这一时刻锁没有被其它线程获取到,则成功获取并持有锁。
能被中断的获取锁 获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
超时获取锁 在指定的时间之前获取锁,如果过了指定的时间任然无法获取到锁,则返回。
方法名称 描述
void lock() 线程获取锁,当获取到锁后,线程从该方法返回。
void lockInterruptibly() throws InteruptedExecption 与lock方法不同之处在于可以在获取锁的过程中中断当前的线程。
boolean tryLock() 尝试获取锁,获取到返回true,未获取到返回false。
boolean tryLock(long time, TimeUnit unit) throws InteruptedException; 尝试获取锁,下列三种情况下会返回:1.在指定的时间内获取到锁。2.过了超时时间为获取到锁。3.当前线程被中断。
void unlock() 释放锁
Condition newCondition() 获取等待通知组件,只有成功获取到了锁,才能创建该组件

lock和synchronized的区别

关于加锁和释放锁方式和原理的不同

  • synchronized是jvm层面提供的关键字,获取锁和释放锁不需要手工干预。synchronized获取锁时会获取锁定对象(静态方法->类对象,普通方法->当前对象,代码块->提供的对象)的对象监视器Monitor。一旦一个线程获取到这个对象的Monitor,其它线程就无法获取。但是同一个线程对这个Monitor可以多次获取(可重入)。对于锁的释放,当方法正常执行结束或者发生异常时,会释放该锁。

  • lock锁是jdk层面提供的锁,可以基于api执行上锁和解锁操作,操作更加的灵活。Lock接口实现的可重入锁ReentLock,其底层是依赖于AQS实现,AQS内部维护了一个同步队列,获取锁的线程会被加入到这个同步队列上面,等待前一个获取锁的节点释放锁时唤醒。ReentLock提供了多种获取锁的方式,可以在获取锁的时候立即响应中断。

    关于等待队列和同步队列的不同

  • synchronized基于对象监视器,调用底层的wait方法时,会将线程加入到等待队列中,内部只维护了一个等待队列,而调用notify或者notifyAll时,会将唤醒的线程加入到同步队列。

  • ReentLock内部可以创建多个等待队列,可以调用await方法将获取到锁的线程加入到等待队列,也可以调用singal,singalAll唤醒线程,将其加入到同步队列中。