CAS
CAS的全称为Compare-And-Swap,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。CAS并发原语体现在JAVA语言中就是 sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能。那么为什么CAS会出现呢?它的作用是怎样的? 实现并发的传统方式是加锁,JAVA中的锁有synchronized和Lock。Lock是基于AQS和CAS实现的,在此先不叙述。对于synchronized锁,JVM在执行它的时候会依赖操作系统的临界区机制。这样的话,每次执行到synchronized锁,都会经历用户态和内核态之间的切换。这个过程的消耗是很大的。而且,大多数时候synchronized锁住的操作是很细粒度的。为了细粒度的操作去经历用户态和内核态之间的切换是低效的做法。 说到这,我想到了线程池。大家知道,当线程创建和销毁的时间大于任务执行的时间时,就需要考虑使用线程池了。但如果和任务执行时间相比,线程创建和销毁的时间很少,那么线程池也可不用。 在synchronized中就是这个问题,当需要同步的操作粒度很细时,使用synchronized是不高效的,这时就有CAS存在的意义了。比如对于i 这种并发计数功能,使用synchronized就大材小用了,而使用CAS来实现就会更加的轻量级,性能更好。因此可以看到java.util.concurrent.atomic包中有类似AtomicInteger这种类。我们来看下AtomicInteger类的核心源码:
代码语言:javascript复制public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current 1;
if (compareAndSet(current, next))
return next;
}
}
}
使用原子类解决线程安全问题,CAS并不能替代synchronized
代码语言:javascript复制package com.yjc.juc;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class Test1 {
private static AtomicInteger count = new AtomicInteger(0);
public static void add() {
count.incrementAndGet();
System.out.println(count);
}
static CountDownLatch downLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i ) {
new Thread(() -> {
for (int j = 0; j < 10; j ) {
add();
}
downLatch.countDown();
}).start();
}
downLatch.await();
System.out.println(count);
}
}
这样最终结果会是一千但是无法保证线程执行的顺序,使用synchronized进行同步的话效率比较低但是能保证线程是安全的,也可以有序的执行
在上面的代码中Unsafe类负责执行CAS并发原语,由JVM转化为汇编。在代码中使用CAS自旋volatile变量的形式实现非阻塞并发。这种方式是CAS的主要使用方式。 CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置。无论如何,告诉我原值。 Java.util.concurrent.atomic包为开发者提供了比synchronized更加细粒度的并发代码控制方式。实际上也是最细粒度的并发代码控制方式。 CAS是乐观锁,是一种冲突重试的机制,在并发竞争不是很激烈的情况下(也是大多数情况),他的性能要好于基于锁的并发性能。因为并发竞争激烈的话,冲突重试的过程很多。
AQS
AQS原理
AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。 AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包
J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。 核心数据结构:双向链表 state(锁状态) 底层操作:CAS
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。 AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
**注意:AQS是自旋锁:**在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。 获取锁通过CAS,那么没有获取到锁,等待获取锁是如何实现的?我们可以看一下else分支的逻辑,acquire方法:
- tryAcquire:会尝试再次通过CAS获取一次锁。
- addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
- acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。