文章目录

  • Lock接口
  • Lock接口中的方法
  • 锁的分类
  • 乐观锁与悲观锁
  • 可重入锁与非可重入锁
  • 公平与非公平
  • 共享锁与排他锁
  • 自旋锁和阻塞锁
  • 中断锁与不可中断
  • 锁优化(JVM帮助实现)


Lock接口

Lock和Syncronized是两个最常见的锁。Lock接口最常见的实现类是ReentrantLock

Synchronized缺点

  • 效率低:锁的释放困难,无法灵活释放锁。
  • 无法知道是否成功获取锁。

Lock接口中的方法

  • lock(): 最普通的方法,不会在异常时自动释放锁。因此最佳实践是,在finally中释放锁, 保证发生异常时锁可以被释放

缺点: 无法被中断,一旦陷入死锁,lock()就会进入永久等待。

  • tryLock(): 尝试获取锁,返回boolean。这个方法是可以立刻返回的。
  • tryLock(long time, TimeUnit ): 等待一定时间,然后判断。
  • lockInterruptibly(): 等待时间无线,等待锁的过程中,线程可以被中断。
  • unlock(): 一定要放在finally()

锁的分类

java中常用的单体锁 工具类_java中常用的单体锁 工具类

乐观锁与悲观锁

  • 互斥锁的缺点:
    阻塞带来的性能缺点。
    死锁缺陷。乐观锁是免疫死锁的。
  • 乐观锁(CAS):认为发生冲突是小概率的。操作时候不锁住对象,在更新时候进行检查,发生了冲突在回滚修复。典型例子:原子类,并发容器。
  • 适用场合:
    悲观锁: 1. 临界区有IO操作;2. 临界区代码复杂; 3. 临界区竞争激烈
    乐观锁: 适合并发写入少,大部分是读取的场合。

可重入锁与非可重入锁

  • 可重入:可以一个线程请求多个锁。可以在一定程度上减小死锁。取得了某把锁的线程可以继续获得这个锁。在拿到锁之后继续可以拿到其他的锁。
  • 源码实现:AQS框架实现的。
    典型例子:ReentrantLock()

公平与非公平

  • 公平锁就是按照线程请求的顺序来分配锁;非公平锁在需要的场合可以插队。

优点:一定程度上,非公平锁是可以提高效率,避免唤醒带来的空档期。
缺点:可能带来饥饿,一些线程永远等不到锁。

公平锁:new ReetrantLock(true)

共享锁与排他锁

一个锁是否只能一个线程持有。典型的共享锁就是ReentantReadWriteLock,对于读是完全没有干扰的,但是写是封闭的。

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    // 读锁和写锁都是可以单独的加锁解锁的
    ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
    ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
  • 读写锁的交互方法:默认不许插队,但是可以锁的降级。

公平锁:完全不许插队
非公平锁:写锁可以随时插队,读锁只在等待队头不是写锁时候插队。

降级:从写锁变为读锁。但是不能反过来。在writelock.lock()情况下可以请求readlock.lock()。升级会容易造成死锁,使用写锁的前提是不能有读锁,因此可能导致死锁(两个同时升级)。

自旋锁和阻塞锁

线程的状态转换消耗的时间比用户执行代码需要的视角还多。这时对线程执行自旋,完成自旋以后再判断是不是可以获取锁。

缺点:如果前面的线程一直占着锁,其实是浪费cpu资源。

atomic包下的类基本都是自旋锁实现的,AtomicInteger的实现,自旋锁的原理是CAS,再调用unsafe自增是,就是一直while死循环,直到修改成功。

  • 适用场合:多核,并发度低,临界区简单。

中断锁与不可中断

典型就是synchronizedreentranlock

锁优化(JVM帮助实现)

  • 自旋锁和自适应:循环了几次之后自己放弃自旋锁的方法。
  • 锁消除:对于没有必要使用的锁不使用
  • 锁粗化:合并几个锁。