(Java锁简介)
JUC包中提供的锁
Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。 JUC包中提供的锁:
- ReentrantLock重入锁,它是一种可重入的独享锁,具有与使用 synchronized 相同的一些基本行 为和语义,但是它的API功能更强大,==ReentrantLock 相当于synchronized 的增强版,具有 synchronized很多所没有的功能。==
- ReentrantReadWriteLock读写锁 ==synchronized和ReentrantLock都是同步互斥锁==,不管是读操作的线程还是写操作的线程,同 时只能有一个线程获得锁,也就是在进行写操作的时候,在写线程进行访问的时候,所有的线 程都会被阻塞。但是其实,读操作是不需要加锁访问的。互斥锁不区分读写,全部加锁实现起 来简单,但是性能会大打折扣。ReentrantReadWriteLock维护了一对关联锁:ReadLock和WriteLock,由词知意,一个读锁 一个写锁,合称“读写锁”。一个是ReadLock(读锁)用于读操作的,一个是WriteLock(写锁)用于 写操作,这两个锁都实现了Lock接口。==读写锁适合于读多写少的场景,基本原则是读锁可以 被多个线程同时持有进行访问,而写锁只能被一个线程持有。==
- StampedLock重入读写锁,JDK1.8引入的锁类型,是对读写锁ReentrantReadWriteLock的增强版
在Java中往往会按照是否含有某一特性来定义锁,下面我们按照锁的特性将锁进行分组归类,帮助大家 更系统的理解Java中的锁。
锁的分类
按上锁方式划分
隐式锁:synchronized
synchronized为Java的关键字,是Java提供的同步机制,当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只能有一个线程执行该代码。==当使用synchronized修饰代码时,并不需要显式的执行加锁和解锁过程,所以它也被称之为隐式锁==。
显式锁:JUC包中提供的锁
JUC中提供的锁都提供了常用的锁操作,加锁和解锁的方法都是显式的,我们称他们为显式锁。
按特性划分
乐观锁/悲观锁(按照线程在使用共享资源时,要不要锁住同步资源,划分为:乐观锁和悲观锁)
悲观锁:比较悲观,总是假设最坏的情况,对于同一个数据的并发操作,悲观锁认为自己在使 用数据的时候一定有别的线程来修改数据,==因此在获取数据的时候会先加锁==,确保数据不会被别的线程修改。
- 实现:JUC的锁、Synchronized
乐观锁:比较乐观,总是假设最好的情况,对于同一个数据的并发操作,乐观锁认为自己在使用数据时不会有别的线程修改数据,所以在获取数据的时候不会添加锁。只有在更新数据的时候才会去判断有没有别的线程更新了这个数据,如果这个数据没有被更新,当前线程将自己修改的数据成功写入:如果数据已经被其他线程更新,则会根据不同的情况执行不同的操作(例如:报错或自动重试)
- 实现:CAS算法,关系型数据库的版本号机制
可重入锁/不可重入锁:按照同一个线程是否可以重复获取同一把锁,划分为:可重入锁和不可重入锁。
重入锁:一个线程可以重复获取同一把锁,不会因为之前已经获取了该锁未释放而被阻塞。在O获得一个锁之后未释放锁之前,再次获得同一把锁时,只会增加获得锁的次数,当释放锁时,会同时减少锁定次数。可重入锁的一个优点是可一定程度避免死锁。 实现:ReentrantLock、synchronized 非重入锁:不可重入锁,与可重入锁相反,同一线程获得锁之后不可再次获取,重复获取会发生死锁。
公平锁/非公平锁:按照多个线程竞争同一锁时需不需要排队,能不能插队,划分为公平锁和非公平锁。
公平锁:多个线程按照申请锁的顺序来获得锁
- 实现:new ReentrantLock(true)。
非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,允许“插队”,有可能后申请的线程比先申请的线程优先获取锁
- 实现:new ReentrantLock(false),synchronized
独享锁/共享锁:按照多个线程能不能同时共享同一个锁,锁被划分为独享锁和排他锁。
独享锁(写锁):独享锁也叫排他锁,是指同一个锁同时只能被一个线程所持有。如果线程A对获得了锁S后,则其他线程只能阻塞等待线程A释放锁S后,才能获得锁S。
- 实现:synchronized,ReentrantLock
共享锁(读锁):同一个锁可被多个线程同时持有。如果线程A对获得了共享锁S后,则其他线程无需等待可以获得共享锁S。
- 实现:ReentrantReadWriteLock的读锁 在ReentrantReadWriteLock维护了一对关联锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁"。ReadLock(读锁)用于读操作的,WriteLock(写锁)用于写操作,读锁是共享锁,写锁是独享锁,读锁可保证在读多写少的场景中,提高并发读的性能,增加程序的吞吐量。
其他常见的锁
自旋锁:获取锁失败时,线程不会阻塞而是循环尝试获得锁,直至获得锁成功。
- 实现:CAS,举例:AtomicInteger#getAndIncrement()
分段锁:在并发程序中,使用独占锁时保护共享资源的时候,基本上是采用串行方式,每次只能有一个线程能访问它。串行操作是会降低可伸缩性,在某些情况下我们可以将锁按照某种机制分解为一组独立对象上的锁,这成为分段锁。
说的简单一点:容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
- 实现:ConcurrentHashMap。
无锁/偏向锁/轻量级锁/重量级锁
- 这四个锁是synchronized独有的四种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。它们是IVM为了提高synchronized锁的获取与释放效率而做的优化。四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。