CAS
(1) 概念:CompareAndSet 一种无锁的原子算法,使用乐观锁, 如:版本控制,CAS可以保证一次的读-改-写操作是原子操作,在单处理器上该操作容易实现,但是在多处理器上实现就有点儿复杂了。
悲观锁:写(增删改)的操作多,读(查)的少 Lock
乐观锁:读的多,写的少
(2) 思想:给你一个期望值,与你现在的值比较,如果相等立即修改,反之不能修改什么也不做CAS(V,E,N)
(3)作用及优点:CAS实现稍微复杂,无锁,不存在阻塞,提高了效率,CPU的吞吐量,性能好
(4)缺点:CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方法:循环时间太长、只能保证一个共享变量原子操作、ABA问题。
- 循环时间太长:如果CAS一直不成功呢?这种情况绝对有可能发生,如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。在JUC中有些地方就限制了CAS自旋的次数,例如BlockingQueue的SynchronousQueue。
- 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量整成一个变量,利用CAS也不错。例如读写锁中state的高地位
- ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。改进:使用 AtomicStampedReference
(5) 场景: - 应用于简单的数据计算
- 适用线程冲突比较小的场景
public class CAS1{
private static volatile int m = 0;
private static AtomicInteger atomicI = new AtomicInteger (0);
public static void increase1(){
m++;//反编译javap -c 这里使用了3条指令,iconst_1、iadd、putstatic,CPU会时间片轮询所以会出现不一致问题
}
public static void increase2(){
atomicI .incrementAndGet();//反编译javap -c 这里使用了1条 invokevirtual 指令
}
public static void main(String[] args) throws Exception{
Thread[] t = new Thread[20];
for(int i = 0; i < 20; i++){
t[i] = new Thread(()->{
CAS1.increase1();
});
t[i].start();
t[i].join();//join方法加入 group main()//join线程有了交互性。就是让t[i]先执行
}
System.out.println(m);//小于20
Thread[] tf = new Thread[20];
for(int i = 0; i < 20; i++){
tf[i] = new Thread(()->{
CAS1.increase2();
});
tf[i].start();
tf[i].join();
}
System.out.println("atomic:"+atomicI.get());
}
}
AtomicInteger 原码分析
unsafe——后门类,调用CPU指令
valueOffset——地址偏移量,有一个初始地址+地址偏移量=真实地址
value——要修改的值,要保证可见性所以使用volatile
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 final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Unsafe原码分析
var1——目标值
var2——期望值
var4——更改值
public final class Unsafe {
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {//通过JVM调用底层指令
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
}
JVM底层实现的代码
就是用Lock和 cmpxchg指令来实现的
incrementAndGet()->unsafe->unsafe.cpp->汇编cmpxchg,用硬件去保证原子性
AQS
AbstractQueueSynchronizer 同步发生器 构建LOCK,封装性比较高,属于JUC包下,作用于子类定义非公共内部帮助类,写锁的时候的一个帮助器,提供获取锁,如:ReentrantLock重入锁也会用到AQS、ReadWriteLock、Condition
AQS定义两种资源共享方式:Exclusive(acquire独占方法,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
- 基本思想:通过内置的 FIFO 同步队列来完成线程争夺资源的管理工作
- 方法:
- acquire(int arg) 以独占模式获取对象,忽略中断
- acquireShared(int arg) 以共享模式获取对象,忽略中断
- tryAcquire(arg) 试图在独占模式下获取对象状态
- tryAcquireShared(int arg) 试图在共享模式下获取对象状态
- release(int arg) 以独占模式释放对象
- releaseShared(int arg) 以共享模式释放对象
- CLH 同步队列:把每个线程看做一个节点Node,一个指针指向pre前驱和有一个指针指向next后继,有一个头结点就也就是同步器的(傀儡节点),整个工作过程如下:
- 创建同步器(傀儡节点)
- 当有新的线程要进入的时候先去获取 volatile state的状态是否等于0 ,如果等于直接执行,反之当 state >=1的时候就要把自己链接到傀儡节点上,傀儡节点的node.head=node1.pre ,傀儡节点的node.tail=node1.next
- 当第二个线程进入的时候发现state 还是 >=1 则将自己链接到前一个线程Node上,将node1.next = node2, node.tail = node2.next
- CLH同步队列的线程节点会进行自旋 去获取锁(tryAcquire),默认是公平竞争锁
AQS内部线程节点
CANCELLED——因中断、完成、退出队列
SIGNAL——节点的继任者被阻塞
CONDITION——条件阻塞
PROPAGATE——共享模式 头结点的状态
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
ReentrantLock
公平锁
非公平锁
ReentrantReadWriteLock
ReentrantReadWriteLock 实现了 ReadWriteLockReadWriteLock有两个方法 readLock() 返回读取锁、writeLock()返回写入锁
这里的读采用的是非锁的机制, 读取者优先或写入者优先强加给锁访问的排序。但是,它确实支持可选的公平策略。主要是读写分离
锁降级
首先获取readLock(),读取之后释放读锁readLock.unLock(),然后去writeLock()获取写锁,wreteLock.lock()给写锁加锁,再要释放写锁之前我们要加上读锁readLock.lock() 最后进行释放写锁writeLock.unLock()这就是锁降级