什么是自旋锁?
CAS
没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。
CAS: Compare and Swap,翻译成比较并交换。 执行函数 CAS(V,E,N)
CAS 有 3 个操作数,内存值 V,旧的预期值 E,要修改的新值 N。当且仅当预期值 E 和内存值 V 相同时,将内存值 V 修改为 N,否则什么都不做
- Cas 是通过硬件指令,保证原子性
- Java 是通过 unsafe jni 技术
原子类: AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 实现。
优缺点
优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控制重试。
缺点:通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题。
CAS实现一把锁逻辑
Cas 无锁机制原理:
- 定义一个锁的状态;
- 状态状态值=0 则表示没有线程获取到该锁;
- 状态状态值=1 则表示有线程已经持有该锁;
实现细节:
CAS 获取锁:
将该锁的状态从 0 改为 1-----能够修改成功 cas 成功则表示获取锁成功
如果获取锁失败–修改失败,则不会阻塞而是通过循环(自旋来控制重试)
CAS 释放锁:
将该锁的状态从 1 改为 0 如果能够改成功 cas 成功则表示释放锁成
完整代码
/**
* @author zyz
* @version 1.0
* @data 2023/7/17 15:09
* @Description: 使用CAS实现一把锁
* 测试
* 1、上锁不使用循环实现
* 1):获取锁、释放锁, 结果有些获取成功,有些获取失败
* 2):获取锁,不释放锁 结果:只有一个线程获取锁成功,其它都失败
* 2、上锁使用循环实现
* 1) 获取锁、释放锁, 全部成功。
* 2) 获取锁,不释放锁, 结果:只有一个线程获取成功,其它线程一直循环等待。CPU飙高
*/
public class UsingCasHandwritingLock {
private AtomicLong cas = new AtomicLong(0);
private Thread lockCurrentThread;
/**
* 获取锁
* 锁是有状态的
* 0 --- 表示没有人持有该锁
* 1 --- 表示该锁已经被线程持有
* 获取成功:cas 0变为1 cas = true
* 获取锁失败 cas false
*/
public Boolean tryLock() {
boolean result = cas.compareAndSet(0, 1);
return result;
}
/**
*:优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控制重试。
* 缺点:通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题;
* @return
*/
// public Boolean tryLock(){
// while (true){
// System.out.println(Thread.currentThread().getName() + ",CAS");
// boolean result = cas.compareAndSet(0,1);
// if(result){
// lockCurrentThread = Thread.currentThread();
// return true;
// }
// }
// }
/**
* 释放锁
*/
public Boolean unLock() {
if (lockCurrentThread != Thread.currentThread()) {
return false;
}
return cas.compareAndSet(1, 0);
}
public static void main(String[] args) {
UsingCasHandwritingLock lock = new UsingCasHandwritingLock();
IntStream.range(1, 10).forEach((i) -> new Thread(() -> {
try {
boolean result = lock.tryLock();
if (result) {
lock.lockCurrentThread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + ",获取锁成功~~~");
} else {
System.out.println(Thread.currentThread().getName() + ",获取锁失败~~~");
}
} catch (Exception ex) {
} finally {
if (lock != null) {
lock.unLock();
}
}
}).start());
}
}
测试结果
1、释放锁(非循环获取锁)
//获取锁
public Boolean tryLock() {
boolean result = cas.compareAndSet(0, 1);
return result;
}
正常释放锁
try {
//做的任务
} catch (Exception ex) {
//抛异常
} finally {
//释放锁
if (lock != null) {
lock.unLock();
}
}
结果:有些线程获取锁成功,有些线程获取锁失败。
说明:线程获取到该锁,并且还在执行任务,未到释放锁的时候。其它线程在获取锁的时候,就会出现获取锁失败的情况。
2、不释放锁(非循环释放锁)
//获取锁
public Boolean tryLock() {
boolean result = cas.compareAndSet(0, 1);
return result;
}
将释放锁的过程取消,获取到该锁的线程一直拿着。
try {
//做的任务
} catch (Exception ex) {
//抛异常
} finally {
//释放锁
// if (lock != null) {
// lock.unLock();
// }
}
结果:只有一个线程获取锁成功,其它线程获取锁均失败
说明:某个线程获取到该锁,并且一直拿着,不释放。其它线程就不能获取到锁。
3、释放锁(循环获取锁)
public Boolean tryLock(){
while (true){
System.out.println(Thread.currentThread().getName() + ",CAS");
boolean result = cas.compareAndSet(0,1);
if(result){
lockCurrentThread = Thread.currentThread();
return true;
}
}
}
当前线程执行完任务后,释放锁
try {
//做的任务
} catch (Exception ex) {
//抛异常
} finally {
//释放锁
if (lock != null) {
lock.unLock();
}
}
结果:线程全部获取到锁。
说明:未获取到锁的线程,通过循环的形式一直尝试获取锁。只要有线程释放了锁。其它线程就去尝试获取锁,获取不到锁的线程,就一直循环尝试获取。直到获取到锁为止。
4、不释放锁 (循环获取锁)
public Boolean tryLock(){
while (true){
System.out.println(Thread.currentThread().getName() + ",CAS");
boolean result = cas.compareAndSet(0,1);
if(result){
lockCurrentThread = Thread.currentThread();
return true;
}
}
}
线程执行完任务后,不释放锁
try {
//做的任务
} catch (Exception ex) {
//抛异常
} finally {
//释放锁
// if (lock != null) {
// lock.unLock();
// }
}
效果:只有一个线程获取到锁,其它线程一直循环尝试获取锁。
说明:当一个线程获取到锁之后,其它线程就一直循环尝试获取锁。但是该线程没有释放锁,就会导致其它线程一直循环尝试获取锁,就会导致CPU飙高。