懵逼的状态:
面试中经常被问到,如何手写一个锁,很多时候一脸懵逼,不知所措,多少年前深有体会,然而回过头来细细分析,只需使用AtomicReference类 即可以轻松搞定。首先咱们先来了解一下AtomicReference。
AtomicReference:
一. AtomicReference 隶属Atomic家族,可以作用于普通对象,保证更新和访问引用对象操作的原子性。
-
主要方法
compareAndSet(V expect, V update) | 基于原子性操作,如果当前值等于期待的值(expect)则更新成新值(update)返回true, 否则返回失败。 |
getAndUpdate(UnaryOperator<V> updateFunction) | 传入UnaryOperator函数,基于原子性操作,自旋判断期待的值是否等于上一个的值(get() 方法获取),如果相等则更新为UnaryOperator函数apply 方法 传入的值,并返回上一个值. |
get() | 返回当前的值 |
updateAndGet(UnaryOperator<V> updateFunction) | 原理同getAndUpdate(),只是如果更新成功返回更新后的值 |
set(V newValue) | 设置当前的值 |
-
底层原理
实现原子性操作:如果匹配期待的值(except)则更新成新值(update)。 CAS 机制需要3个操作值:
a:需要读写变量的内存位置 V;
b:旧的预期值 A;
c:准备设置新值 B;
通过原子性操作,如果旧的值匹配A则更新成B否则不更新,一般结合自旋模式,不断尝试重复执行这个流程,直到更新成功。
public final boolean weakCompareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
二. 使用AtomicReference 手写锁,基于CAS 机制实现
- 模拟5个线程竞争获取锁,通过compareAndSet方法匹配当前线程如匹配成功,则表示获取锁成功,具体代码如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @description: TODO
* @author: ppx
* @date: 2023/8/1 10:08
* @version: 1.0
*/
public class MyCustomLock {
private final static AtomicReference<Thread> reference = new AtomicReference<>();
/**
* @description: 获取获得锁
* @param:
* @return: void
* @author: ppx
* @date: 2023/8/1 14:41
*/
public void lock() {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + ",尝试获得锁");
//通过cas 机制 自旋尝试获取锁
while (!reference.compareAndSet(null, currentThread)) {
// System.out.println(currentThread.getName() + "自旋尝试去获得锁wait......");
}
// 执行到,证明已获取到锁
System.out.println(currentThread.getName() + ",已获得锁");
}
/**
* @description: 释放锁
* @param:
* @return: void
* @author: ppx
* @date: 2023/8/1 14:42
*/
public void unlock() {
Thread currentThread = Thread.currentThread();
//通过cas 机制 自旋匹配当前线程
while (!reference.compareAndSet(currentThread, null)) {
}
System.out.println(currentThread.getName() + ",已释放锁");
}
/**
* @description: 模拟5个线程 竞争获取锁
* @param: args
* @return: void
* @author: ppx
* @date: 2023/8/1 14:45
*/
public static void main(String[] args) {
MyCustomLock lock = new MyCustomLock();
for (int i = 1; i < 6; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " ,业务处理...");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "线程" + i).start();
}
}
}
执行结果:
线程1,尝试获得锁
线程3,尝试获得锁
线程2,尝试获得锁
线程1,已获得锁
线程4,尝试获得锁
线程5,尝试获得锁
线程1 ,业务处理...
线程1,已释放锁
线程2,已获得锁
线程2 ,业务处理...
线程2,已释放锁
线程4,已获得锁
线程4 ,业务处理...
线程4,已释放锁
线程3,已获得锁
线程3 ,业务处理...
线程3,已释放锁
线程5,已获得锁
线程5 ,业务处理...
线程5,已释放锁
实现一个锁,如此Easy。