synchronized锁升级及CAS和AQS简述

概述

为什么会有锁升级的步骤呢,假设没有这个步骤,多个线程竞争时,抢到锁的线程直接运行,其他的都直接sleep/wait,然后等第一个线程运行完成后,再由操作系统唤醒接下来的线程。这个一套动作下来就很费调度资源.

所以锁的升级相当于加了一层缓存,实在是竞争的很激烈,再由操作系统介入

平时分布式环境下控制并发都是用的redis中间件。
但在一些mini项目中对多线程的控制,简单操作可以用synchronized关键字来控制;

synchronized上锁时,锁的信息都放在对象头上,对象除了自己的数据外还有头部信息.

在这里插入图片描述

锁升级过程

锁的升级过程,其实就是线程争抢锁的过程;
第一个线程获取锁后,会切换到偏向锁,之后当前线程可重复进入锁住的代码块,此时第二个线程来获取锁,就会升级到轻量级级锁,然后第二个线程CAS自旋等待,自旋失败到一定次数后还没获取到锁,此时就会升级为重量级锁.

cas,比较并交换
cas算法的过程是这样的,cas包括有三个值:
v表示要更新的变量
e表示预期值,就是旧的值
n表示新值
更新时,判断只有e的值等于v变量的当前旧值时,才会将n新值赋给v,更新为新值。
否则,则认为已经有其他线程更新过了,则当前线程什么都不操作,最后cas放回当前v变量的真实值

AQS

AQS,即AbstractQueuedSynchronizer, 队列同步器,它是Java并发用来构建锁和其他同步组件的基础框架。其定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch.

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中.

CLHCraig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO)AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

在这里插入图片描述

获取同步状态(加锁)
假设线程A要获取同步状态(这里想象成锁,方便理解),初始状态下state=0,所以线程A可以顺利获取锁,A获取锁后将state置为1。在A没有释放锁期间,线程B也来获取锁,此时因为state=1,表示锁被占用,所以将B的线程信息和等待状态等信息构成出一个Node节点对象,放入同步队列,headtail分别指向队列的头部和尾部(此时队列中有一个空的Node节点作为头点,head指向这个空节点,空Node的后继节点是B对应的Node节点,tail指向它),同时阻塞线程B(这里的阻塞使用的是LockSupport.park()方法)。后续如果再有线程要获取锁,都会加入队列尾部并阻塞。

释放同步状态(解锁后其他线程获取锁)

线程A释放锁时,即将state置为0,此时A唤醒头节点的后继节点(所谓唤醒,其实是调用
LockSupport.unpark(B)方法),即B线程LockSupport.park()方法返回,此时B发现state已经为0
所以B线程可以顺利获取锁,B获取锁后BNode节点随之出队。

以ReentrantLock为例

其中非公平锁的加锁流程大致如下
java.util.concurrent.locks.ReentrantLock.NonfairSync

cas判断下当前是否有锁,没有锁直接占用;

有锁时判断是否是当前线程占用(相同线程可重复入锁)
非持有锁的线程则加入到等待队列中(双向链表)
一旦进入到AQS链表中,都是按照FIFO顺序去获取锁了(这点公平锁和非公平锁都一样)
非公平锁只是在一开始获取锁的时候可以不排队互相竞争,之后进入AQS队列后线程之间还是得排队的;还有一个特殊的点是非公平锁在当前有其他线程在排队时会直接尝试获取锁,而不是像公平锁一样有其他线程在排队时会直接加入到AQS队列中去排队

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值