Redis缓存:万字长文!从底层开始带你了解并发编程 下

简介: Redis缓存:万字长文!从底层开始带你了解并发编程

Thread.interrupted()会清除当前线程的中断标记位。

整个获取锁的流程:

1.如果尝试获取锁成功,直接返回。

2.没成功,先加入到等待队列尾部,标记为独占模式。

3.尝试这获取一次锁后,如果还是获取不到就去休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。

4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

这也就是ReentrantLock.lock()的流程

release(int)

此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
                //唤醒等待队列里的下一个线程
            return true;
        }
        return false;
    }

它是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自定义同步器在设计tryRelease()的时候要明确这一点!!tryRelease(int) 此方法尝试去释放指定量的资源。

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

还是需要AQS的实现类自己去写。 unparkSuccessor(Node) 此方法用于唤醒等待队列中下一个线程。

    private void unparkSuccessor(Node node) {
        //这里,node一般为当前线程所在的结点。
        int ws = node.waitStatus;
        if (ws < 0)
            //置零当前线程所在的结点状态,允许失败。
            compareAndSetWaitStatus(node, ws, 0);
        //找到下一个需要唤醒的结点s
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {//如果为空或已取消
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);//唤醒
    }

用unpark()唤醒等待队列中最前边的那个未放弃线程。 释放锁的流程 1.释放指定锁的资源并返回结果。 2.如果成功释放,就唤醒等待队列中最前边的那个未放弃线程。 3.如果没成功,返回false。

共享锁

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

此方法用于将当前线程加入等待队列尾部休息,直到其他线程释放资源唤醒自己,自己成功拿到相应量的资源后才返回。

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//成功
                        setHeadAndPropagate(node, r);//将head指向自己,还有剩余资源可以再唤醒之后的线程
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

跟独占模式比,这里只有线程是head.next时(“老二”),才会去尝试获取资源,有剩余的话还会唤醒之后的队友。那么问题就来了,假如老大用完后释放了5个资源,而老二需要6个,老三需要1个,老四需要2个。老大先唤醒老二,老二一看资源不够,他是把资源让给老三呢,还是不让?答案是否定的!老二会继续park()等待其他线程释放资源,也更不会去唤醒老三和老四了。独占模式,同一时刻只有一个线程去执行,这样做未尝不可;但共享模式下,多个线程是可以同时执行的,现在因为老二的资源需求量大,而把后面量小的老三和老四也都卡住了。当然,这并不是问题,只是AQS保证严格按照入队顺序唤醒罢了(保证公平,但降低了并发)。 == setHeadAndPropagate(Node, int)==

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

== doReleaseShared()==

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

自定义锁

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

    isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
    tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
    tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
    tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
    tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

自定义一个简单的锁

/**
 * @author yhd
 * @createtime 2020/9/8 9:44
 */
public class MLock implements Lock {
    private AbstractQueuedSynchronizer sync=new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
    }
    @Override
    public boolean tryLock() {
        return false;
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
    @Override
    public void unlock() {
        sync.release(1);
    }
    @Override
    public Condition newCondition() {
        return null;
    }
    //自定义一个独占锁
    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
# 最后我们该如何学习?
**1、看视频进行系统学习**
这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。
另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/59569ae6c88695e0dc7cc749f66014b6.png)
**2、读源码,看实战笔记,学习大神思路**
“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。
Spring源码深度解析:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/18aa1bf64527b019561188427118ca00.png)
Mybatis 3源码深度解析:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/c94b3f2f286c7990cd2bf4e0d0cb9fc3.png)
Redis学习笔记:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/e3e81600df834d35de63fad72242e32a.png)
Spring Boot核心技术-笔记:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/b2c32afd4c7be63e65d8fb55f32abcb3.png)
**3、面试前夕,刷题冲刺**
面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。
关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:
![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/ecca26959f1f68f48e8cb86cd0301b53.png)
只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。
人生短暂,别稀里糊涂的活一辈子,不要将就。
**资料领取方式:[点击蓝色传送门免费领取上述资料](https://gitee.com/vip204888/java-p7)**
文章内容中涉及到的Java面试题、源码文档,技术笔记等学习资料,均可以免费分享给大家学习,只需你动动手多多支持即可!
源码深度解析:
[外链图片转存中...(img-PbPbNROu-1628139638931)]
Mybatis 3源码深度解析:
[外链图片转存中...(img-BuaQYvto-1628139638933)]
Redis学习笔记:
[外链图片转存中...(img-Jr6q12s2-1628139638935)]
Spring Boot核心技术-笔记:
[外链图片转存中...(img-WGdImeQE-1628139638937)]
**3、面试前夕,刷题冲刺**
面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。
关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:
[外链图片转存中...(img-q0aI0cq6-1628139638938)]
只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。
人生短暂,别稀里糊涂的活一辈子,不要将就。
**资料领取方式:[点击蓝色传送门免费领取上述资料](https://gitee.com/vip204888/java-p7)**
文章内容中涉及到的Java面试题、源码文档,技术笔记等学习资料,均可以免费分享给大家学习,只需你动动手多多支持即可!


相关文章
|
6月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
658 25
|
6月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
1108 3
|
7月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
318 1
Redis专题-实战篇二-商户查询缓存
|
7月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
11月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
存储 缓存 NoSQL
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
快速学习 Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
|
存储 缓存 NoSQL
Redis 缓存|学习笔记
快速学习Redis 缓存
232 0
|
缓存 NoSQL 安全
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
快速学习6.0Spring Boot 2.0实战 Redis 分布式缓存6.0。
639 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
|
缓存 NoSQL Redis
首页数据显示-添加 redis 缓存(3)| 学习笔记
快速学习 首页数据显示-添加 redis 缓存(3)
292 0
首页数据显示-添加 redis 缓存(3)| 学习笔记
|
缓存 NoSQL Java
首页数据显示-添加 redis 缓存(1) | 学习笔记
快速学习 首页数据显示-添加 redis 缓存(1)
370 0
首页数据显示-添加 redis 缓存(1) | 学习笔记
下一篇
开通oss服务