1:AQS简介
- AQS(AbstractQueuedSynchronizer):j.u.c下的Lock就是使用AQS实现的;为了使得多线程在并发访问资源的时候的安全性,纯Java语言实现(其中synchronized底层是由c++实现的)
- AQS支持线程抢占两种锁——独占锁和共享锁:
- 独占锁:同一个时刻只能被一个线程占有,如ReentrantLock,ReentrantWriteLock等,它又可分为:
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
- 共享锁:同一时间点可以被多个线程同时占有,如ReentrantReadLock,Semaphore等
2:unsafe魔术类
- 直接绕过虚拟机,操作底层的内存。方法大都是native方法(底层是c++)
- Java不建议使用此类,如果需要使用,则必须使用反射机制进行实例化;
- Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。
- 使用 ReentrantLock 进行多线程开发,当一个线程抢占锁失败时,线程将被挂起,实现线程挂起的正是Unsafe类。
3:手写公平独占AQS
- 尝试获取锁
//尝试进行加锁的算法
public boolean acquire(){
//CAS比较与交换算法,保证任意时刻只有一个线程可以拿到
//当前线程
Thread current = Thread.currentThread();
//获取到当前state初始值
int c = getState();
if( c == 0){ //目前锁还没有被持有
//如果等待队列中没有进程(实现公平锁)或者当前线程是等待队列中第一个线程,并且此线程修改成功了(加锁成功),则设置持有锁的线程为本线程;
if((waiters.size()==0||current == waiters.peek())&&compareAndSwapState(0,1)){
setLocalHolder(current);
return true;
}
}
return false;
}
- 加锁:
- 任意时刻,只能有一个线程加锁成功(原子性)
- 使用CAS机制(CAS,一种乐观锁机制,如果对象当前值和期望值一样,那么则将对象的值设置成新值。和悲观锁不一样,它不需要抢占锁,它是一种尝试性的,能有效地提高效率,它的全称是 compareAndSwap ,依赖于硬件的原子操作实现。)
public void lock(){
//如果加锁成功
if(acquire()){
return;
}
//当前线程
Thread current = Thread.currentThread();
//没有获取成功,将线程放入等待队列中
waiters.add(current);
//如果没有加锁成功,则使此线程一直自旋在本方法
for (;;){
//让步出线程
//1:Thread.yield();但是循环之后还是在占用cpu,不推荐
//2:Thread.sleep(1000);不推荐,原因如下
//(1):设置时常大之后,其他线程已经释放锁,本线程还在睡眠,浪费时间
//(2):设置时常小之后,导致不停的睡眠启动线程,系统开销大
//3:Thread.wait();不推荐,因为在唤醒线程的时候,无法准确指定唤醒那一个线程;
//4:使用Unsafe类中的park()和unpark()方法,进行手动的释放和开启线程(此两种方法已经重写在了jdk的LockSupport类中)
/*
//jdk中的方法体
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, (Object)null);
}
*/
//判断当前线程是否是第一个等待的线程(保证公平),如果是则继续循环获取锁,获取成功跳出循环
if((current==waiters.peek()) && acquire()){
//第一个线程获取到锁之后,将它从等待队列中移除
waiters.poll();
return;
}
//阻塞当前线程(将此线程的所有数据放入内存中的运行时数据区)
LockSupport.park(current);
}
- 解锁
//解锁方法
public void unLock(){
//判断当前对象是不是之前拿到锁的对象
if(Thread.currentThread()!=localHolder){
throw new RuntimeException("LocalHolder is not current thread");
}
//将state和LocalHolder都置为空,表示当前锁空闲
int state = getState();
if(compareAndSwapState(state,0)){
setLocalHolder(null);
//当前锁空闲后,如果等待队列中有线程,则唤醒此线程
Thread first = waiters.peek();
if(first!=null){
LockSupport.unpark(first);
}
}
}
- 使用队列waiters来存放当前等待获取锁的线程
//定义一个线程安全(底层是使用CAS算法保证线程安全的)的队列,用于保存此时没有获取到锁的线程
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();
- 通过反射机制获得Unsafe对象
//通过反射机制获取到Unsafe对象
private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
package util;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeInstance {
//获取Unsafe对象
public static Unsafe reflectGetUnsafe() {
//通过反射机制获取到Unsafe类
Field field = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 全部代码
package lock;
import sun.misc.Unsafe;
import util.UnsafeInstance;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.LockSupport;
public class AQS {
//用来记录当前加锁状态,记录加锁次数,
//值为0/1,为1时表示已经有一个线程持有了锁
private volatile int state = 0;
//表示当前只有锁的对象
private Thread localHolder;
//定义一个线程安全(底层是使用CAS算法保证线程安全的)的队列,用于保存此时没有获取到锁的线程
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Thread getLocalHolder() {
return localHolder;
}
public void setLocalHolder(Thread localHolder) {
this.localHolder = localHolder;
}
//尝试进行加锁的算法
public boolean acquire(){
//CAS比较与交换算法,保证任意时刻只有一个线程可以拿到
//当前线程
Thread current = Thread.currentThread();
//获取到当前state初始值
int c = getState();
if( c == 0){ //目前锁还没有被持有
//如果等待队列中没有进程(实现公平锁)或者当前线程是等待队列中第一个线程,并且此线程修改成功了(加锁成功),则设置持有锁的线程为本线程;
if((waiters.size()==0||current == waiters.peek())&&compareAndSwapState(0,1)){
setLocalHolder(current);
return true;
}
}
return false;
}
//加锁
public void lock(){
//如果加锁成功
if(acquire()){
return;
}
//当前线程
Thread current = Thread.currentThread();
//没有获取成功,将线程放入等待队列中
waiters.add(current);
//如果没有加锁成功,则使此线程一直自旋在本方法
for (;;){
//让步出线程
//1:Thread.yield();但是循环之后还是在占用cpu,不推荐
//2:Thread.sleep(1000);不推荐,原因如下
//(1):设置时常大之后,其他线程已经释放锁,本线程还在睡眠,浪费时间
//(2):设置时常小之后,导致不停的睡眠启动线程,系统开销大
//3:Thread.wait();不推荐,因为在唤醒线程的时候,无法准确指定唤醒那一个线程;
//4:使用Unsafe类中的park()和unpark()方法,进行手动的释放和开启线程(此两种方法已经重写在了jdk的LockSupport类中)
/*
//jdk中的方法体
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, (Object)null);
}
*/
//判断当前线程是否是第一个等待的线程(保证公平),如果是则继续循环获取锁,获取成功跳出循环
if((current==waiters.peek()) && acquire()){
//第一个线程获取到锁之后,将它从等待队列中移除
waiters.poll();
return;
}
//阻塞当前线程(将此线程的所有数据放入内存中的运行时数据区)
LockSupport.park(current);
}
}
//解锁方法
public void unLock(){
//判断当前对象是不是之前拿到锁的对象
if(Thread.currentThread()!=localHolder){
throw new RuntimeException("LocalHolder is not current thread");
}
//将state和LocalHolder都置为空,表示当前锁空闲
int state = getState();
if(compareAndSwapState(state,0)){
setLocalHolder(null);
//当前锁空闲后,如果等待队列中有线程,则唤醒此线程
Thread first = waiters.peek();
if(first!=null){
LockSupport.unpark(first);
}
}
}
/*
* 原子操作。
* @param except:目前值
* @param update:要更新后的值
*/
public final boolean compareAndSwapState(int except,int update){
return unsafe.compareAndSwapInt(this,stateOffset,except,update);
}
//通过反射机制获取到Unsafe对象
private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
//在内存的偏移量值,因为CAS种需要此参数
private static long stateOffset;
static {
try {
//找到state对象在内存中的偏移量
stateOffset = unsafe.objectFieldOffset(AQS.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
4:测试锁
- 测试代码:(新建10个线程,执行m方法,自增一个共享变量10000次,期望输出100000)
- 但由于没有保证原子性,值永远小于100000;
package main;
import lock.AQS;
import java.util.ArrayList;
import java.util.List;
public class AQSTest {
//共享变量
private static int count = 0;
//创建手写的锁
private static AQS syn = new AQS();
//线程执行的方法
public void m() {
//syn.lock();
for (int i = 0; i < 10000; i++) {
count++;
}
//syn.unLock();
}
public static void main(String[] args) {
AQSTest t=new AQSTest();
List<Thread> threads=new ArrayList<>();
//创建10个线程,并执行m方法
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m,"thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//输出值
System.out.println(count);
}
}
- 测试结果:
5:加上手写锁之后
- 测试结果:
视频地址:同步器核心原理刨析