【Java】手动模拟实现一个AQS
- 什么是AQS?
synchronized(基于底层C++,语言实现的同步机制)
Aqs同步器(Java实现)
- 【Unsafe】魔法类
绕过虚拟机,直接操作底层的内存
- 话不多说,我们手动模拟一个AQS:
1》锁对象:
package com.example.demo.thread.current; import com.example.demo.util.UnsafeInstance; import sun.misc.Unsafe; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.LockSupport; /** * 公平锁 * * @author Code Farmer * @date 2020/5/28 22:47 */ public class AqsLock { /** * 当前加锁状态,记录加锁的次数 */ private volatile int state = 0; /** * 当前持有锁的线程 */ private Thread lockHolder; private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();//基于CAS保证入队出队安全 public int getState() { return state; } public void setState(int state) { this.state = state; } public Thread getLockHolder() { return lockHolder; } public void setLockHolder(Thread lockHolder) { this.lockHolder = lockHolder; } /** * 尝试获取锁 * * @return */ private boolean acquire() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//同步器还没有加锁 // if (waiters.size() == 0 || compareAndSwapInt(0, 1)) { //如果这么写,可能会导致有某一个线程获取到锁,然后poll出后,队列为空,导致下一个线程又可以直接获取到锁 if ((waiters.size() == 0 || current == waiters.peek()) && compareAndSwapInt(0, 1)) { this.setLockHolder(current); return true; } } return false; } /** * 加锁 */ public void lock() { if (acquire()) {//加锁成功 return; } else { Thread current = Thread.currentThread(); waiters.add(current);//保存对线程的引用 for (; ; ) { //让出cpu使用权 //Thread.yield();//会持续浪费CPU //Thread.sleep(1000) //为什么不使用sleep?虽然使用sleep会减少cpu的使用,但是1000ms时间可能会发生这样事情,200ms的时候可能上一个线程释放锁,然后其他线程要阻塞800ms,如果是一个用户业务,那个用户体验会非常差 //Thread.sleep(1); //1ms会造成频繁切换上下文而造成cpu浪费 if ((current == waiters.peek()) && acquire()) { waiters.poll();//唤醒后,把自身从队列中移出 return; } //阻塞当前线程,释放cpu资源 LockSupport.park(current); } } } /** * 解锁 */ public void unlock() { if (Thread.currentThread() != lockHolder) { throw new RuntimeException("lock holder is not current thread"); } int state = getState(); if (compareAndSwapInt(state, 0)) { setLockHolder(null); Thread first = waiters.peek(); if (first != null) { LockSupport.unpark(first);//由于唤醒线程是随机唤醒,我们不能保证唤醒的是哪一个线程,所以需要在获取锁时判断是不是等于队列第一个线程 } } } public final boolean compareAndSwapInt(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe(); private static long stateOffset; /** * 计算state变量偏移量 */ static { try { stateOffset = unsafe.objectFieldOffset(AqsLock.class.getDeclaredField("state")); } catch (NoSuchFieldException e) { e.printStackTrace(); } } }
2》Unsafe实例对象:
package com.example.demo.util; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * @author Code Farmer * @date 2020/5/28 23:42 */ public class UnsafeInstance { public static Unsafe reflectGetUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } return null; } }
3》业务中实现加锁:
package com.example.demo.service.impl; import com.example.demo.service.AqsDemoService; import com.example.demo.thread.current.AqsLock; /** * @author Code Farmer * @date 2020/5/28 22:54 */ public class AqsDemoServiceImpl implements AqsDemoService { AqsLock aqsLock = new AqsLock(); @Override public String decStockNoLock() { aqsLock.lock(); //一个查询操作(略) //一个更新操作(略) aqsLock.unlock(); return null; } }
- 这里阻塞和唤醒用了【Unsafe】中的【park】和【unpark】
#会把cpu缓存当中运行时数据全部清除,保存至内存(RSS)当中
LockSupport.park(current);
#唤醒线程
LockSupport.unpark(current);
- 图解一下上述代码的过程:
学而不思则罔 思而不学则殆 !