Java中锁的分类

1、公平锁/非公平锁

  • 公平锁是指多个线程按照申请锁的顺序来获取锁
  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序(直接上来就抢),有可能后申请的线程比先申请的线程优先获取锁;
  • 非公平锁可能会造成优先级反转或者进程饥饿现象;
  • 非公平锁的吞吐量更大;

进程饥饿(Starvation):
指当等待时间给进程推进和响应带来明显影响称为进程饥饿。

吞吐量:
单位时间内成功地传送数据的数量

对于ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。
对于Synchronized而言,也是一种非公平锁。

2、可重入锁

  • 又名递归锁
  • 可重复可递归调用的锁
  • 对一个线程,在外层使用锁之后,在内层仍然可以使用(自动获取锁),并且不发生死锁;
  • 可重入锁的一个好处是可一定程度避免死锁;

对于ReentrantLock而言,他的名字就可以看出是一个可重入锁。
对于Synchronized而言,也是一个可重入锁。

3、独享锁/共享锁

  • 独享锁是指该锁一次只能被一个线程所持有;
  • 共享锁是指该锁可被多个线程所持有;
  • 读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的;

对于ReentrantLock而言,其是独享锁。
对于Synchronized而言,其是独享锁。

对于Lock的另一个实现类ReentrantReadWriterLock里面有两把锁:ReadLock(读锁)和WriterLock(写锁),合称“读写锁”。再进一步观察可以发现ReadLock和WriterLock是靠内部Sync实现的锁。Sync是AQS的一个子类,这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在。

在ReentrantReadWriterLock里面,读锁和写锁的主体都是Sync,但读锁和写锁的加锁方式不一样。读锁是共享锁,写锁是独享锁。
读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriterLock并发性相比一般的互斥锁有了很大提升。

4、互斥锁/读写锁

  • 互斥锁/读写锁是所得一个具体的实现;
  • 互斥锁在Java中的具体实现就是ReentrantLock
  • 读写锁在Java中的具体实现就是ReadWriteLock
  • 读写锁中:读锁是共享锁,写锁是独享锁;

5、乐观锁/悲观锁

  • 悲观锁就是传统意义上的“锁”;
  • 乐观锁就是“不锁”,只是在拿数据之前先判断别人有没有更新这个数据;

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

6、分段锁

  • 分段锁其实是一种锁的设计
  • 即字面意思:给不同的数据段加锁
  • 通常并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

7、对象的四种锁状态(详细)

  1. 无锁状态
  2. 偏向锁状态
  3. 轻量级锁状态
  4. 重量级锁状态

(1)锁的升级和降级

  • 几种锁会随着竞争情况逐渐升级,锁的升级很容易发生;
  • 降级发生的条件会比较苛刻,锁降级发生在Stop The World期间,当JVM进入安全点的时候,会检查是否有闲置的锁,然后进行降级。

关于锁降级有两点说明:
1.不同于大部分文章说锁不能降级,实际上HotSpot JVM 是支持锁降级的,文末有链接。
2.上面提到的Stop The World期间,以及安全点,这些知识是属于JVM的知识范畴,本文不做细讲。
摘录自:http://concurrent.redspider.group/article/02/9.html

(2)一个对象的锁信息放在哪里?

  • Java的锁都是基于对象的;
  • 锁放在Java对象头的Mark Word中;

(3)什么是Java对象头?

  • 每个Java对象都有对象头;
  • 如果是非数组类型,则用2个字宽来存储对象头,如果是数组,则会用3个字宽来存储对象头;
  • 在32位处理器中,一个字宽是32位;在64位虚拟机中,一个字宽是64位;

对象头的内容:

长度内容说明
32/64bitMark Word存储对象的hashCode或锁信息等
32/64bitClass Metadata Address存储到对象类型数据的指针
32/64bitMark Word存储对象的hashCode或锁信息等

Mark Word的格式:

锁状态29 bit 或 61 bit是否是偏向锁?(1 bit)锁标志位(2 bit)
无锁001
偏向锁线程ID101
轻量级锁指向栈中锁记录的指针此时这一位不用于标识偏向锁00
重量级锁指向互斥量(重量级锁)的指针此时这一位不用于标识偏向锁10
GC标记此时这一位不用于标识偏向锁11

Mark Word存储的内容:

  • 偏向锁:偏向的线程ID + 偏向锁标志;
  • 轻量级锁:指向线程栈中Lock Record的指针;
  • 重量级锁:指向堆中的monitor对象的指针。

(4)偏向锁

  • 偏向锁在资源无竞争情况下消除了同步语句,连CAS操作都不做了,提高了程序的运行性能;
  • 对锁置个变量,如果发现为true,代表资源无竞争,则无需再走各种加锁/解锁流程。如果为false,代表存在其他线程竞争资源,那么就会走后面的流程。(锁升级)
实现原理

一个线程在第一次进入同步块时,会在对象头和栈帧中的锁记录里存储锁的偏向的线程ID。当下次该线程进入这个同步块时,会去检查锁的Mark Word里面是不是放的自己的线程ID

  • 如果是,表明该线程已经获得了锁,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁 ;
  • 如果不是,就代表有另一个线程来竞争这个偏向锁。这个时候会尝试使用CAS来替换Mark Word里面的线程ID为新线程的ID,这个时候要分两种情况:
    • 成功,表示之前的线程不存在了, Mark Word里面的线程ID设置为新线程的ID,锁不会升级,仍然为偏向锁;
    • 失败,表示之前的线程仍然存在,那么暂停之前的线程,设置偏向锁标识为 0,并设置锁标志位为00,升级为轻量级锁,会按照轻量级锁的方式进行竞争锁。

CAS: Compare and Swap
比较并设置。用于在硬件层面上提供原子性操作。在 Intel 处理器中,比较并交换通过指令cmpxchg实现。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。

线程竞争偏向锁的过程如下:

在这里插入图片描述
图中涉及到了lock record指针指向当前堆栈中的最近一个lock record,是轻量级锁按照先来先服务的模式进行了轻量级锁的加锁。

撤销偏向锁

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁。

偏向锁升级成轻量级锁时,会暂停拥有偏向锁的线程,重置偏向锁标识,这个过程看起来容易,实则开销还是很大的,大概的过程如下:

在一个安全点(在这个时间点上没有字节码正在执行)停止拥有锁的线程。
遍历线程栈,如果存在锁记录的话,需要修复锁记录和Mark Word,使其变成无锁状态。
唤醒被停止的线程,将当前锁升级成轻量级锁。
所以,如果应用程序里所有的锁通常出于竞争状态,那么偏向锁就会是一种累赘,对于这种情况,我们可以一开始就把偏向锁这个默认功能给关闭:

-XX:UseBiasedLocking=false
偏向锁的获得和撤销(升级):

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值