Lock基本使用及原理分析

JUC并发工具包

java.util.concurrent

Lock

重入锁,记录获得锁的次数
ReentrantLock实现Lock
Lock基本使用及原理分析_第1张图片

ReentrantLock原理

AQS(同步工具)

AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在Node节点之间的关联关系

AQS实现线程同步,同步组件
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。

AQS是自旋锁:**在等待唤醒的时候,经常会使用自旋的方式,不停地尝试获取锁,直到被其他线程获取成功

AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
Lock基本使用及原理分析_第2张图片

AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier

AQS基本实现
Lock基本使用及原理分析_第3张图片
.lock()去获取锁原理。
Lock基本使用及原理分析_第4张图片
表示当前线程获取锁成功

非公平锁允许插队

CAS

jvm层面实现原子操作。
实现原子性,和内存中值相比(state和expect相比较,相等update,cas成功了),将0变为1,说明线程获取锁成功,返回boolean值true,实现锁的获得,乐观锁的方式
0是无锁状态,大于0是有锁状态。
这是第一个线程,
在这里插入图片描述
第二个线程cas失败,走acquire
先去cas再次尝试
else if 表示同一个线程,增加重入次数
Lock基本使用及原理分析_第5张图片
如果上面返回false,执行里面的内容
Lock基本使用及原理分析_第6张图片
addWaiter方法将线程封装为Node。EXCLUSIVE表示独占锁
Lock基本使用及原理分析_第7张图片
首先new一个Node。tail 和prev 是AQS中组成双向链表的东西
Lock基本使用及原理分析_第8张图片
三个线程访问:
一开始是null,所以走enq,一种自旋方式
一开始初始化,将tail和head放到Node
第一次初始化,第二次循环执行else,构建双向链表结构的Node
第三次执行不是在自旋中执行,而是外面执行,代码和else一样,也是一种优化
Lock基本使用及原理分析_第9张图片
第一个·线程不用参与竞争,后面两个线程需要竞争,加入队列

Lock基本使用及原理分析_第10张图片
执行完成addWaite后,返回当前封装好的Node
然后执行下面逻辑
Lock基本使用及原理分析_第11张图片
循环将前面的节点变成-1,SIGNAL后,挂起,线程b和c变成阻塞状态,唯一可以运行的就是线程A。只有线程A释放锁后,才能唤醒
Lock基本使用及原理分析_第12张图片
线程a调用unlock释放锁。
无非就是讲state修改为0,唤醒下一个节点
Lock基本使用及原理分析_第13张图片
一个节点删除

是否获得锁,返回false会将其放到aqs队列中

ReentrantLock(重入互斥锁)

再次调用,不会阻塞,而是增加重入次数。synchronized也是
后续释放减少重入次数

解决死锁

使用

操作灵活,使用api去获取锁和释放锁
Lock基本使用及原理分析_第14张图片

读写锁

ReentrantReadWriteLock

如果只是读取数据,无需加锁,读多写少场景提升性能
Lock基本使用及原理分析_第15张图片
Lock基本使用及原理分析_第16张图片
如果是读,不会阻塞,如果是一个线程在写,其他读取数据,会阻塞。保证读取的最新值。

所以,适用场景就是读多写少

ReenTrantLock可重入锁和synchronized的区别

可重入性:

从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

锁的实现:

Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。

性能的区别:

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

功能区别:

便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized

ReenTrantLock独有的能力:

  1.  ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
    
  2.  ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
    
  3.  ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
    

你可能感兴趣的:(锁,java)