懵逼的状态:
面试中经常被问到,如何手写一个锁,很多时候一脸懵逼,不知所措,多少年前深有体会,然而回过头来细细分析,只需使用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。