JUC原理之锁机制原理一

计算机底层简单介绍

在这里插入图片描述

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值