Java~并发编程CAS和AQS原理刨析,阿里大牛纯手码7W字

// 设置值为 newValue

public final void set(int newValue) {

value = newValue;

}

//返回旧值,并设置新值为 newValue

public final int getAndSet(int newValue) {

/**

  • 这里使用for循环不断通过CAS操作来设置新值

  • CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系

  • */

for (;😉 {

int current = get();

if (compareAndSet(current, newValue))

return current;

}

}

// 原子的设置新值为update, expect为期望的当前的值

public final boolean compareAndSet(int expect, int update) {

//本质上还是使用unsafe的native方法实现的, 就是硬件的原子操作帮助实现CAS

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

// 获取当前值current,并设置新值为current+1

public final int getAndIncrement() {

for (;😉 {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return current;

}

}

// 此处省略部分代码,余下的代码大致实现原理都是类似的

}

ABA问题

  • 这个问题就是加入现在有个num为0 有一个线程把他修改为1, 然后紧接着又有一个线程把他修改为0了 那此时仅仅通过CAS的比较是无法区分的

  • 解决这个问题就需要引入额外的信息 (给变量加一个版本号 每次进行修改 都递增版本号)

  • 从Java 1.5开始, JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  • 是乐观锁的一种实现,在竞争小的时候很高效。

public boolean compareAndSet(V expectedReference,

V newReference,

int expectedStamp,

int newStamp) {

Pair current = pair;

return

expectedReference == current.reference &&

expectedStamp == current.stamp &&

((newReference == current.reference &&

newStamp == current.stamp) ||

casPair(current, Pair.of(newReference, newStamp)));

}

耗能问题

  • 一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多(查看getAndSet(),可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。

  • 改进方法就是通过一个计数比如循环100次就不在循环, 直接进行阻塞等待.

实现计数器

  • 在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。

public class Counter {

private final AtomicInteger atomicInteger;

public Counter(int val) {

this.atomicInteger = new AtomicInteger(val);

}

private int getCount() {

return atomicInteger.get();

}

private void increase() {

atomicInteger.getAndIncrement();

}

private void set(int newVal) {

atomicInteger.set(newVal);

}

}

什么是AQS


  • AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。

  • 这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。

  • 所以说他的内部主要就是维护了一个表示状态的int值和一个FIFO的阻塞队列

static final class Node {

/** Marker to indicate a node is waiting in shared mode */

static final Node SHARED = new Node();

volatile int waitStatus;

volatile Node prev;

volatile Node next;

volatile Thread thread;

Node nextWaiter;

final boolean isShared() {

return nextWaiter == SHARED;

}

final Node predecessor() throws NullPointerException {

Node p = prev;

if (p == null)

throw new NullPointerException();

else

return p;

}

Node() { // Used to establish initial head or SHARED marker

}

Node(Thread thread, Node mode) { // Used by addWaiter

this.nextWaiter = mode;

this.thread = thread;

}

Node(Thread thread, int waitStatus) { // Used by Condition

this.waitStatus = waitStatus;

this.thread = thread;

}

}

/**

  • The synchronization state.

*/

private volatile int state;

AQS实现原理

  • AQS的实现主要在于维护一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。队列中的每个节点是对线程的一个封装,包含线程基本信息,状态,等待的资源类型等。

  • 此类支持默认排他模式或者共享模式或者都支持

  • 当以独占方式进行获取时,其他线程尝试进行的获取将无法成功.比如Condition由只持独占模式的子类用作实现

  • 由多个线程获取的共享模式, 当成功获取共享模式时,下一个等待线程(如果存在)也必须确定它是否也可以获取

  • 在不同模式下等待的线程共享相同的FIFO队列。通常,实现子类仅支持这些模式中的一种,但是这两种都可以在例如中使用 ReadWriteLock。仅支持独占模式或仅支持共享模式的子类无需定义支持未使用模式的方法。

  • CLH结构如下:

  •  +------+  prev +-----+       +-----+
    
  • head | | <---- | | <---- | | tail

  •  +------+       +-----+       +-----+
    
  • AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态.

  • 请求锁不成功的线程会被addWaiter方法负责把当前无法获得锁的线程包装为一个Node添加到队尾

final boolean acquireQueued(final Node node, int arg) {

try {

boolean interrupted = false;

for (;😉 {

final Node p = node.predecessor();

if (p == head && tryAcquire(arg)) {

setHead(node);

p.next = null; // help GC

return interrupted;

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} catch (RuntimeException ex) {

cancelAcquire(node);

throw ex;

}

}

  • AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过CAS操作

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

分享一些系统的面试题,大家可以拿去刷一刷,准备面试涨薪。

这些面试题相对应的技术点:

  • JVM
  • MySQL
  • Mybatis
  • MongoDB
  • Redis
  • Spring
  • Spring boot
  • Spring cloud
  • Kafka
  • RabbitMQ
  • Nginx

大类就是:

  • Java基础
  • 数据结构与算法
  • 并发编程
  • 数据库
  • 设计模式
  • 微服务
  • 消息中间件

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

  • 数据结构与算法
  • 并发编程
  • 数据库
  • 设计模式
  • 微服务
  • 消息中间件

[外链图片转存中…(img-5DlpsXWW-1711750331550)]

[外链图片转存中…(img-xva7u9A0-1711750331551)]

[外链图片转存中…(img-KYmH1joi-1711750331551)]

[外链图片转存中…(img-NJgvLawJ-1711750331551)]

[外链图片转存中…(img-zDG0VyZT-1711750331552)]

[外链图片转存中…(img-tuyjtfnw-1711750331552)]

[外链图片转存中…(img-2Yag5ggY-1711750331553)]

[外链图片转存中…(img-6XjXJl4N-1711750331553)]

[外链图片转存中…(img-tQjwOLjl-1711750331554)]

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值