计算机底层简单介绍
JUC出现的原因?
为java语言提供多线程同步结构的支撑
JUC解决了什么问题?
用于解决多线程同步问题,给Java开发者提供便利的函数和功能、结构
JUC包含了哪些内容?
原子类、锁类、工具类(线程同步结构、线程池等等)
如何实现一把锁?
1、如何表示当前锁状态:无锁?有锁?
boolean state; //true 有锁,false无锁
为了实现锁重入,那么我需要记录锁重入次数
int times;
=>两个变量有点冗余了,所以我们直接用int state;来表示锁状态:
0:无锁 大于0重入次数 特别地:1为重入一次,也即只加锁一次
2、如何保证多线程抢锁线程安全
CAS
lock cmpxchg
3、如何处理没有获取到锁的线程
(1)自旋:线程不停的执行某个代码步骤,直到条件满足或次数达到限制
缺点:耗费CPU资源
优点:适用于操作的时间短且步骤少的操作,自旋一会儿马上就能获得锁,这样不会太占用CPU资源。
注意:当CPU个数增加且线程数增加的情况下,优点会退化成自旋锁的缺点
使用场景:争用较少且代码量小的临界区
(2)阻塞:达不到条件,告诉操作系统,把我阻塞
选用规则:自旋锁消耗的时间大于线程阻塞线程上下文切换的时间选用阻塞,否则选用自选锁。
(3)自旋+阻塞
4、如何释放锁
自旋:自己抢锁
阻塞:唤醒
通过如上可以得出什么是AQS?
Abstract:因为它并不知道怎么上锁,模板方法设计模式即可,暴露出上锁逻辑
Queue:阻塞队列
Synchronizer:同步
CAS+state完成多线程抢锁逻辑
Queue 完成抢不到锁的线程排队
AQS核心代码
获取锁的代码。
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类判定获取锁失败返回false,那么这里取反,表示为 true
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取失败后添加到阻塞队列
selfInterrupt();
}
// 子类实现获取锁的逻辑,AQS并不知道你怎么用这个state来上锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
释放锁的代码。
public final boolean release(int arg) {
// 子类判定释放锁成功
if (tryRelease(arg)) {
// 检查阻塞队列唤醒即可
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 子类实现获取锁的逻辑,AQS并不知道你怎么用这个state来上锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
总结
子类只需要实现自己的获取锁逻辑和释放锁逻辑即可,至于排队阻塞等待、唤醒机制均由AQS来完成。
ReentrantLock原理
概念:基于AQS时间的可重入锁实现类
核心变量和构造器
公平锁与非公平锁
为什么非公平锁性能高于公平锁?
因为公平锁需要排队,加上唤醒都需要时间,(上下文切换加调度延迟时间)B要先转换为running状态,然后丢到运行队列里面,然后通过操作系统调度算法调度执行。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public ReentrantLock() {
// 默认为非公平锁。为何默认为非公平锁?因为通过大量测试下来,发现非公平锁的性能优于公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 由fair变量来表明选择锁类型
sync = fair ? new FairSync() : new NonfairSync();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 非公平锁标准获取锁方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 当执行到这里时,正好获取所得线程释放了锁,那么可以尝试抢锁
if (c == 0) {
// 继续抢锁,不看有没有线程排队
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程就是持有锁的线程,表明锁重入
else if (current == getExclusiveOwnerThread()) {
// 利用state整形变量进行次数记录
int nextc = c + acquires;
// 如果超过了int表示范围,表明符号溢出,所以抛出异常0111 1111 + 1 = 1000 0000
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 返回false 表明需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待唤醒获取锁
return false;
}
// 公平锁和非公平锁公用方法,因为在释放锁的时候,并不区分是否公平
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 如果当前线程不是上锁的那个线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 不是重入锁,那么当前线程一定是释放锁了,然后我们把当前AQS用于保存当前锁对象的变量ExclusiveOwnerThread设置为null,表明释放锁成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 注意:此时state全局变量没有改变,也就意味着在setState之前,没有别的线程能够获取锁,这时保证了以上的操作原子性
setState(c);
// 告诉AQS,我当前释放锁成功了,你可以去唤醒正在等待锁的线程了
return free;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
static final class NonfairSync extends Sync {
// 由ReentrantLock调用获取锁
final void lock() {
// 非公平锁,直接抢锁,不管有没有线程排队
if (compareAndSetState(0, 1))
// 上锁成功,那么标识当前线程为获取锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢锁失败,进入AQS的标准获取锁流程
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 使用父类提供的获取非公平锁的方法来获取锁
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
// 由ReentrantLock调用
final void lock() {
// 没有尝试抢锁,直接进入AQS标准获取锁流程
acquire(1);
}
// AQS调用,子类自己实现获取锁的流程
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 此时有可能正好获取锁的线程释放了锁,也有可能本身就没有线程获取锁
if (c == 0) {
// 注意:这里和非公平锁的区别在于:hasQueuedPredecessors看看队列中是否有线程正在排队,没有的话再通过CAS抢锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 抢锁成功
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程就是获取锁的线程,那么这里是锁重入,和非公平锁操作一模一样
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 返回false 表明需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待唤醒获取锁
return false;
}
}
}
核心方法
1、获取锁操作
public void lock() {
// 直接通过sync同步器上锁
sync.lock();
}
2、释放锁操作
public void unlock() {
sync.release(1);
}