Java中的锁
1.锁的 7 大分类
- 偏向锁/轻量级锁/重量级锁
- 可重入锁/非可重入锁
- 共享锁/独占锁
- 公平锁/非公平锁
- 悲观锁/乐观锁
- 自旋锁/非自旋锁
- 可中断锁/不可中断锁
2.偏向锁/轻量级锁/重量级锁
偏向锁
如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只需要打个标记就行了,这就是偏向锁的思想。一个对象被初始化后,还没有任何线程来获取它的锁时,那么它就是可偏向的,当有第一个线程来访问它并尝试获取锁的时候,它就将这个线程记录下来,以后如果尝试获取锁的线程正是偏向锁的拥有者,就可以直接获得锁,开销很小,性能最好。轻量级锁
JVM 开发者发现在很多情况下,synchronized 中的代码是被多个线程交替执行的,而不是同时执行的,也就是说并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决,这种情况下,用完全互斥的重量级锁是没必要的。轻量级锁是指当锁原来是偏向锁的时候,被另一个线程访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的形式尝试获取锁,而不会陷入阻塞。重量级锁
重量级锁是互斥锁,它是利用操作系统的同步机制实现的,所以开销相对比较大。当多个线程直接有实际竞争,且锁竞争时间长的时候,轻量级锁不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态。
锁升级的路径:无锁→偏向锁→轻量级锁→重量级锁。
3.可重入锁/非可重入锁
可重入锁指的是线程当前已经持有这把锁了,能在不释放这把锁的情况下,再次获取这把锁。不可重入锁指的是虽然线程当前持有了这把锁,但是如果想再次获取这把锁,也必须要先释放锁后才能再次尝试获取。
可重入锁作用和使用场景
- 最大的作用是避免死锁
- 当一个线程执行一个带锁的代码块或方法,同时代码块或方法里也获取了同一个锁。为了避免死锁此时就可以使用可重入锁。
4.共享锁/独占锁
共享锁指的是我们同一把锁可以被多个线程同时获得,而独占锁指的就是,这把锁只能同时被一个线程获得。读写锁中的读锁,是共享锁,而写锁是独占锁。读锁可以被同时读,可以同时被多个线程持有,而写锁最多只能同时被一个线程持有。
5.公平锁/非公平锁
公平锁的公平的含义在于如果线程现在拿不到这把锁,那么线程就都会进入等待,开始排队,在等待队列里等待时间长的线程会优先拿到这把锁,。而非公平锁会在一定情况下忽略掉已经在排队的线程,发生插队现象
6.悲观锁/乐观锁
悲观锁会悲观的认为多个线程操作数据一定会产生问题,因此在获取资源之前必须先拿到锁。
而乐观锁则恰恰相反,认为同一个数据的并发操作是不会发生修改的,因此在更新数据的时候会采用尝试更新,不断更新数据,乐观的认为不加锁的并发操作是没有问题的。
乐观锁的实现:
CAS:当多个线程尝试使用CAS同时更新一个变量时,只有其中一个线程能更新变量的值,而其他线程都会失败,失败的线程并不会挂起,而是被告知这次竞争中失败,并且可以再次尝试,CAS是一种非阻塞式的同步方式。
CAS操作包含三个操作数——内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置更新为新值。否则不做任何操作。
乐观锁是一种思想,而CAS是这种思想的一种实现方式
版本号控制:一般在数据表中加上一个数据版本号version字段,表示数据被修改的次数。当数据被修改时,version值会+1。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值与当前数据库中的version值相等才会更新数据,否则重试更新操作,直到更新成功
7.自旋锁和非自旋锁
自旋锁的理念是如果线程现在拿不到锁,并不直接陷入阻塞或者释放CPU资源,而是开始利用循环,不停尝试获取锁,这个循环过程被称为“自旋”。相反,非自旋锁就是没有自旋的过程,如果拿不到锁就直接放弃或者进行其他的逻辑处理。
8.可中断锁/不可中断锁
在Java中,synchronized关键字修饰的锁代表的是不可中断锁,一旦线程申请了锁,就只能等待拿到锁之后才能进行其他的逻辑处理,而ReentrantLock就是一种可中断锁,在获取锁的过程中突然不想获取了也可以在中断之后去做其他的事情,并不需要一直等到获取到锁才离开。
