锁是多线程并发问题中的重要组成,接着上一篇文章,今天就简单总结一下Java中各种锁如何分类。

Java中锁分为以下几种:

  • 乐观锁、悲观锁
  • 独享锁、共享锁
  • 公平锁、非公平锁
  • 互斥锁、读写锁
  • 可重入锁
  • 分段锁
  • 锁升级(无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁) JDK1.6

这些锁的分类并不全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。


1、乐观锁 & 悲观锁

两种锁只是一种概念

乐观锁:乐观锁认为一个线程去拿数据的时候不会有其他线程对数据进行更改,所以不会上锁。

实现方式:CAS机制、版本号机制

悲观锁:悲观锁认为一个线程去拿数据时一定会有其他线程对数据进行更改。所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。比如Java里面的synchronized关键字的实现就是悲观锁。实现方式:就是加锁。

2、独享锁 & 共享锁

两种锁只是一种概念

独享锁:该锁一次只能被一个线程所持有

共享锁:该锁可以被多个线程所持有

举例:

synchronized是独享锁;

可重入锁ReentrantLock是独享锁;

读写锁ReentrantReadWriteLock中的读锁ReadLock是共享锁,写锁WriteLock是独享锁。

独享锁与共享锁通过AQS(AbstractQueuedSynchronizer)来实现的,通过实现不同的方法,来实现独享或者共享。

3、互斥锁 & 读写锁

上面讲的独享锁/共享锁就是一种概念,互斥锁/读写锁是具体的实现。

互斥锁的具体实现就是synchronized、ReentrantLock。ReentrantLock是JDK1.5的新特性,采用ReentrantLock可以完全替代替换synchronized传统的锁机制,更加灵活。

读写锁的具体实现就是读写锁ReadWriteLock。

4、可重入锁

定义:对于同一个线程在外层方法获取锁的时候,在进入内层方法时也会自动获取锁。

优点:避免死锁

举例:ReentrantLock、synchronized

5、公平锁 & 非公平锁

公平锁:多个线程相互竞争时要排队,多个线程按照申请锁的顺序来获取锁。

非公平锁:多个线程相互竞争时,先尝试插队,插队失败再排队,比如:synchronized、ReentrantLock

6、分段锁

分段锁并不是具体的一种锁,只是一种锁的设计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。CurrentHashMap底层就用了分段锁,使用Segment,就可以进行并发使用了,而HashMap确实非线程安全的,就差在了分段锁上。

7、偏向锁 & 轻量级锁 & 重量级锁

JDK 1.6 为了减少获得锁和释放锁所带来的性能消耗,在JDK 1.6里引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

研究发现大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了不让这个线程每次获得锁都需要CAS操作的性能消耗,就引入了偏向锁。当一个线程访问对象并获取锁时,会在对象头里存储锁偏向的这个线程的ID,以后该线程再访问该对象时只需判断对象头的Mark Word里是否有这个线程的ID,如果有就不需要进行CAS操作,这就是偏向锁。当线程竞争更激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式等待一会儿上一个线程就会释放锁,但是当自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁,重量级锁就是Synchronized,重量级锁会使除了此时拥有锁的线程以外的线程都阻塞。