一.概述:
锁分为可重入锁和不可重入锁,这两者的区别是什么?
可重入锁就是当一个线程获得了某个对象的实例并进入一个方法A,这个线程在没有释放锁的情况下能否再次进入方法A,就是可重入锁和不可重入锁的区别。
- 可重入锁:能再次进入方法A,什么情况下需要再次进入方法A,对喽,就是递归调用这个加锁方法执行运算的时候,像synchronized和Reentrantlock都是可重入锁。
- 不可重入锁:不能再次进入方法A,Mutex(互斥锁)是不可重入锁。
注:在看我这篇文章之前,建议先阅读以下几篇文章,对重入锁有一个大概的了解,顺序也按照我给出的顺序来阅读,我这篇文章只是对可重入锁的源码、CAS、AQS等进行一个总结,对于没有看过AQS或可重入锁的朋友来说不太容易懂。踏下心来看完我推荐的文章,基本对可重入锁(甚至互斥锁及其他锁)有一个比较深入的了解。
1.先学习一下可重入锁的使用方法
https://blog.csdn.net/u012545728/article/details/80843595
https://blog.csdn.net/soonfly/article/details/70918802
2.简单介绍可重入锁的原理,就像作者起的题目一样:轻松学习java可重入锁,这篇文章把可重入锁的机制解释的非常易懂生动。
https://blog.csdn.net/yanyan19880509/article/details/52345422
3.解释完了,直接看源码吧,这篇文章是我看过比较好的一篇介绍可重入锁源码的文章:
https://www.jianshu.com/p/7e1a1903d467
4.看完第三篇,是不是想了解下AQS:强烈推荐下面这篇讲解AQS的文章,妈呀,当我看完这篇文章之后,那叫一个通透,终于知道什么叫大牛,就是不单技术好,博客写的也NB
AQS:https://www.cnblogs.com/waterystone/p/4920797.html
5.读完AQS,是不是也想扫盲下CAS,那来吧:这篇把CAS连带Unsafe类将的比较通俗易懂
CAS:https://blog.csdn.net/mmoren/article/details/79185862
6.最后,记不记得第三篇文章提到了CAS自旋volatile变量,如果还感兴趣的朋友可以参考下这篇文章:
https://blog.csdn.net/holmofy/article/details/73824757
二.可重入锁机制及源码总结:
可重入锁分公平锁和非公平锁:线程老老实实在同步队列排队机制的锁叫公平锁,在之前线程释放锁期间可以加塞的锁叫非公平锁,可重入锁的默认锁是非公平锁。读完上面几篇文章后,可结合我的总结的源码图进行巩固(图片尺寸比较大,下载看更清楚,我传的是高清图),公平锁和非公平锁机制类似,因此这里只拿不公平锁举例。
可重入锁里面有三个内部类,Sync同步类,非公平锁类,公平锁类,其中Sync类给公平锁和非公平锁实现了一些共有方法,比如tryRelease()等方法,也给非公平锁实现了一些特定的比如nonfairTryAcquire()方法(还真是非公平啊...)。
可重入锁最基本最重要的方法就是lock()加锁和unLock()释放锁方法,这里重点介绍这两个方法。
三.自定义同步器AQS:
谈到并发,不得不谈Reentrantlock,而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer,就像名字描述的,抽象队列同步器定义了一套多线程访问共享资源的同步器框架,许多类的实现都依赖它,比如
ReentrantLock、Semaphore、CountDownLatch等。
AQS维护了一个volatile state(代表共享资源)和FIFO线程等待队列(多线程竞争资源队列),不同自定义同步器竞争共享资源的方式不同,但自定义同步器在实现时只需实现共享资源state的获取与释放方式即可,至于线程等待队列的维护,AQS已经实现好了,主要有下面几个方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
AQS定义两种资源共享方式:独占模式(EXCLUSIVE,只有一个线程能执行,如Reentrantlock)和共享模式(Semaphore、CountDownLatch)。一般来说,自定义同步器要么是独占的,要么是共享的,也可兼有两种模式,比如ReentrantReadWritelock。我们实现某种模式只需要继承AQS,实现tryAcquire和tryRelease方法即可,这也是AQS不是接口而是类的原因,用什么模式只要实现那个模式对应的方法。
四.CAS:
CAS全称CompareAndSwap,核心算法是这样的:
CAS(V,E,N)//V-要更新的变量;E-旧值,或者叫期望值;N-要替换的新值;
在替换为新值之前,比较下变量V是否是当前的期望值,如果相等,替换,不相等,不做动作或重复检测。
锁分为悲观锁和乐观锁,在多线程环境下,
- 悲观锁:如果在执行动作之前会认为可能发生锁冲突,在执行动作之前加锁的机制为悲观锁,对待操作是一种悲观的态度;
- 乐观锁:在执行动作之前认为不会发生锁冲突,也不加锁,即无所操作,一旦发生所冲突再去解决冲突的机制为乐观锁,对待操作是一种乐观的态度;
而CAS就是一种乐观锁,他是一种系统级语言,因为系统原语的字节码指令执行时连续的,所以说CAS操作时一条CPU的原子指令操作,不会造成数据不一致的问题。
Unsafe类
我们接着深入一下,CAS在Java的实现环境中必须依赖鲜为人知的Unsafe类,可以理解为CAS操作是Unsafe类的一些方法。Unsafe这个类的方法都是被native()方法修饰的,意思是它的方法可以向操作C指针一样直接操作物理内存,直接调用操作系统底层资源执行相应任务。Unsafe类里CAS的相关操作涉及的方法如下:
//o为指定对象,offset为对象内存的偏移量,偏移量迅速定位字段并设置或获取该字段的值,
expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
- 线程挂起与恢复
在Java中,线程的挂起与恢复的相关操作被封装在LockSuport类中,但是其底层的还是通过Unsafe类的park()和unPark()方法实现的, - CAS使用场景
从Java1.5开始,提供了java.util.concurrent.atomic包,在该包中有很多基于CAS操作实现的类,比如
AtomicBoolean:原子更新布尔类型
AtomicInteger:原子更新整型
AtomicLong:原子更新长整型
比如AtomicInteger的原子自增方法getAndAddInt()就是实现了CAS操作,从而保证了线程安全,有兴趣的朋友可以去看下源码。
后记:由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!