一、Lock锁的大体逻辑过程
下面的逻辑过程很重要,下面的逻辑就是整体上道格李设计Lock锁的逻辑。
在阅读AQS+ReentrantLock源码的时候,细节部分都是基于这个大体过程慢慢填充进去的。 并且在掌握大体逻辑过程之后,再去看AQS+ReentrantLock的源码真的方便很多。
逻辑如下:
ReentrantLock lock = new ReentrantLock(false);//false是非公平锁,true是公平锁
T1,T2,T3三个线程要执行下面代码的逻辑;
只有T1可以执行,那么T2和T3两个线程放哪??
lock.lock()
1. T1获取锁成功,而T2和T3进入循环
while(true){ //当加锁失败不会立即释放cpu,而是进行CAS自旋,即:令线程入队成功,然后等待被唤醒,最后重新获取锁资源。
if(CAS获取锁成功){ //在并发时,lock是通过cas的方式对其中【一个线程】进行加锁成功的
break; //为什么是CAS获取锁成功呢?lock是通过修改state状态进行获取锁资源的,如果修改成功则获取锁资源
}
//如果没有获取锁成功,线程进入【阻塞】状态, 线程把自己的运行状态保存到内存里面,【等待被唤醒】
// 这个内存肯定是个容器:可能是队列,也可能是hashset. 具体是哪个根据真实的业务场景选择。
HashSet.add(Thread) //该集合存储线程对象的引用(地址)
LinkedQueued.put(Thread);
LockSupport.park(); //不需要深挖park是怎么实现的,只需要知道park可以阻塞线程。
}
2. T1获取到锁,执行代码. 执行结束后,T1执行unlock操作,从集合里面取出下一个线程尝试获取锁。
//我们的逻辑
xxxxxx
xxxxxx
lock.unlock()
//想一想,如果线程一直阻塞,每个线程都有一个线程栈空间,那么我们的内存不就溢出了吗?
//所以线程不是一直阻塞的,而是等着别人来【唤醒】自己。
//不是唤醒全部线程,而是唤醒【指定的线程】。
Thread t = HashSet.get();
Thread t = LinkedQueued.take();
LockSupport.unpark(t); //通过线程引用,唤醒这个线程
二、Lock锁的三大核心
1.自旋(while);
2.LockSuport;
3.CAS(改变锁状态state,从无锁状态0改成状态1有锁,或者可重入锁);
一个重要的数据结构:queue队列,存储阻塞的线程。