- 什么是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);
- 图解一下上述代码的过程:
来源:oschina
链接:https://my.oschina.net/u/4300166/blog/4297167