1.锁的类别
7种
- 偏向锁/轻量级锁/重量级锁
- 可重入所/非可重入锁
- 共享锁/独占锁
- 公平锁/非公平锁
- 悲观锁/乐观锁
- 自旋锁/非自旋锁
- 可中断锁/非可中断锁
2.特点
-
偏向锁/轻量级锁/重量级锁
- 这三种锁特指 synchronized 锁的状态,通过在对象头中的 mark word 来表明锁的状态。
- 偏向锁:如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只需要打个标记就行了,这就是偏 向锁的思想。一个对象被初始化后,还没有任何线程来获取它的锁时,那么它就是可偏向的,当有第一个线程来访问它并尝试获取锁的时候,它就将这个线程记录下来,以后如果尝试获取锁的线程正是偏向锁的拥有者,就可以直接获得锁,开销很小,性能最好
- 轻量级锁:JVM 开发者发现在很多情况下,synchronized 中的代码是被多个线程交替执行的,而不是同时执行 的,也就是说并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决,这种情况下,用完全互斥的重量级锁是没必要的。轻量级锁是指当锁原来是偏向锁的时候,被另一个线程访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的形式尝试获取锁,而不会陷入阻塞。
- 重量级锁:重量级锁是互斥锁,它是利用操作系统的同步机制实现的,所以开销相对比较大。当多个线程直接有实际竞争,且锁竞争时间长的时候,轻量级锁不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态。
- 你可以发现锁升级的路径:无锁→偏向锁→轻量级锁→重量级锁。 综上所述,偏向锁性能最好,可以避免执行 CAS 操作。而轻量级锁利用自旋和 CAS 避免了重量级锁带 来的线程阻塞和唤醒,性能中等。重量级锁则会把获取不到锁的线程阻塞,性能最差。
-
可重入锁/非可重入锁
- 可重入锁:线程当前已经持有这把锁了,能在不释放这把锁的情况下再次获得这把锁,
- 同理,不可重入就是线程虽然持有这把锁,但是如果想再次获取这把锁就必须先释放锁然后再次尝试获取
- 对于可重入过而言,最典型的就是ReentrantLock了,正如名字,reentrant就是可重入,是Lock接口最主要的一个实现类
-
共享锁/独占锁:同一把锁可以被多个线程同时获得,独占锁是这把锁这能被一个线程获得,读写锁就最好诠释了共享锁和独占锁的理念,读写锁中的读锁是共享锁,写锁是独占锁,读锁可以被同时读,可以同时被多个线程持有,写锁最多之鞥呢同时被一个线程持有
-
公平锁/非公平锁:如果线程现在拿不到这把锁,那么线程就都进入等待,开始排队,在等待队列等待时间长的线程优先拿到这把锁,先来先得。非公平锁在一定情况忽略已经在排队的线程,发生插队现象
-
悲观锁/乐观锁:悲观锁:在获取资源之前必须先拿到锁,达到独占状态,当前线程在操作资源的时候,其他线程由于不能拿到锁,所以其他线程不能来影响我。 而乐观锁恰恰相反,它并不要求在获取资源前拿到锁,也不会锁住资源;相反,乐观锁利用 CAS理念,在不独占资源的情况下,完成了对资源的修改。
-
自旋锁/非自旋锁:自旋锁的理念是如果线程现在拿不到锁,并不直接陷入阻塞或者释放CPU 资源,而是开始利用循环,不停地尝试获取锁,这个循环过程被形象地比喻为 “自旋”,就像是线程在 “自我旋转”。相反,非自旋锁的理念就是没有自旋的过程,如果拿不到锁就直接放弃,或者进行其他的处理逻辑,例如去排队、陷入阻塞等。
-
可中断锁/非可中断锁:在 Java 中,synchronized 关键字修饰的锁代表的是不可中断锁,一旦线程申请了锁,就没有回头路了,只能等到拿到锁以后才能进行其他的逻辑处理。而我们的ReentrantLock 是一种典型的可中断锁,例如使用lockInterruptibly 方法在获取锁的过程中,突然不想获取了,那么也可以在中断之后去做其他的事情,不需要一直傻等到获取到锁才离开。
\