【JAVA多线程】JDK中的各种锁,看这一篇就够了

目录

1.概论

1.1.实现锁的要素

1.2.阻塞队列

1.3.Lock接口和Sync类

2.各种锁

2.1.互斥锁

2.1.1.概论

2.1.2.源码

1.lock()

2.unlock()

2.2.读写锁

2.3.Condition

2.3.1.概论

2.3.2.底层实现


1.概论

1.1.实现锁的要素

JAVA中的锁都是可重入的锁,因为不可重入的试用的时候很容易造成死锁。这个道理很好想明白:

当一个线程已经持有一个锁,并在持有该锁的过程中再次尝试获取同一把锁时,如果没有重入机制,第二次请求会被阻塞,因为锁已经被自己持有。这会导致线程自我死锁,因为它在等待自己释放的锁。

可重入是指获取锁的线程可以继续重复的获得此锁。其实我们想都能想到要实现一把锁需要些什么,首先肯定是:

  • 标志位,也叫信号量,标记锁的状态和重入次数,这样才能完成持有锁和释放锁。

接下来要考虑的是拒接策略,当前锁被持有期间,后续的请求线程该怎么处理,当然可以直接拒绝,JAVA的选择委婉点,选择了允许这些线程躺在锁上阻塞等待锁被释放。要实现让线程躺在锁上等待,我们想想无非要:

  • 需要支持对一个线程的阻塞、唤醒

  • 需要记录当前哪个线程持有锁

  • 需要一个队列维护所有阻塞在当前锁上的线程

OK,以上四点就是JAVA锁的核心,总结起来就是信号量+队列,分别用来记录持有者和等待者。

1.2.阻塞、唤醒操作

首先我们来看看阻塞和唤醒的操作,在JDK中提供了一个Unsafe类,该类中提供了阻塞或唤醒线程的一对操作 原语——park/unpark:

public native void unpark(Object var1);
public native void park(boolean var1, long var2);

这对原语最终会调用操作系统的程序接口执行线程操作。

1.2.阻塞队列

拿来维护所有阻塞在当前锁上的线程的队列能是个普通队列吗?很显然不是,它的操作必须是线程安全的是吧,所以这个队列用阻塞队列实现才合适。什么是阻塞队列:

阻塞队列提供了线程安全的元素插入和移除操作,并且在特定条件下会阻塞线程,直到满足操作条件。

说到JDK中的阻塞队列,其核心就是AbstractQueuedSynchronizer,简称AQS,由双向链表实现的一个元素操作绝对安全的队列,用来在锁的实现中维护阻塞在锁上的线程上的队列的这个角色。

来看看AQS的源码:

它有指向前后节点的指针、有一个标志位state、还有一个提供线程操作原原语(阻塞、唤醒)的unsafe类。

所以其实AQS就长这样:

点进源码可以看到其随便一个方法都是线程安全的:

由于本文不是专门聊AQS这里就不扩展了,反正知道AQS是一个线程安全的阻塞队列就对了。

1.3.Lock接口和Sync类

JAVA中所有锁的顶级父接口,用来规范定义一把锁应该有那些行为职责:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

JAVA中所有锁的实现都是依托AQS去作为阻塞队列,每个锁内部都会实现一个Sync内部类,在自身Sync内部以不同的策略去操作AQS实现不同种类的锁。

abstract static class Sync extends AbstractQueuedSynchronizer {......}

2.各种锁

2.1.互斥锁

2.1.1.概论

ReentrantLock,互斥锁,ReentrantLock本身没有任何代码逻辑,依靠内部类Sync干活儿:

public class ReentrantLock implements Lock, Serializable {
    private final ReentrantLock.Sync sync;
    public void lock() {
        this.sync.lock();
    }
    public void unlock() {
        this.sync.release(1);
    }
    ......
}

ReentrantLock的Sync继承了AQS

abstract static class Sync extends AbstractQueuedSynchronizer {......}

Sync是抽象类,有两个实现:

  • NonfairSync,公平锁

  • FairSync,非公平锁

实例化ReentrantLock的实例时,根据传入的标志位可以创建公平和公平的实现

public class ReentrantLock implements Lock, java.io.Serializable{
public ReentrantLock() {
        sync = new NonfairSync();
    }
​
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    ......
}
}

2.1.2.源码

1.lock()

公平锁的lock():

static final class FairSync extends Sync {
        final void lock() {
            acquire(1);//进来直接排队
        }

非公平锁的lock():

static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))//进来直接抢锁
                setExclusiveOwnerThread(Thread.currentThread());//将锁的持有者设置为当前线程
            else
                acquire(1);//没抢过再去排队
        }
    }

acquire()是AQS的模板方法:

tryAcquire,尝试再去获取一次锁,公平锁依然是排队抢,去看看阻塞队列是否为空;非公平锁依然是直接抢。

acquireQueued,将线程放入阻塞队列。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquireQueued(..)是lock()最关键的一部分,addWaiter(..)把Thread对象加入阻塞队列,acquireQueued(..)完成对线程的阻塞。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//如果发现自己在队头就去拿锁
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//调用原语,阻塞自己
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued(..)函数有一个返回值,表示什么意思 呢?虽然该函数不会中断响应,但它会记录被阻塞期间有没有其他线 程向它发送过中断信号。如果有,则该函数会返回true;否则,返回false。所以才有了以下逻辑:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

当 acquireQueued(..) 返回 true 时,会调用 selfInterrupt (),自己给自己发送中断信号,也就是自己把自己的中断标志位设 为true。之所以要这么做,是因为自己在阻塞期间,收到其他线程中 断信号没有及时响应,现在要进行补偿。这样一来,如果该线程在loc k代码块内部有调用sleep()之类的阻塞方法,就可以抛出异常,响 应该中断信号。

2.unlock()

unlock的逻辑很简单,每次unlock,state-1,直到state=0时,将锁的拥有者置null,释放锁。由于只有锁的持有线程才能操作lock,所以unlock()不需要用CAS,操作时直接判断一下是不是锁的持有线程在操作即可。

public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {//释放锁
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒阻塞队列中的后继者
            return true;
        }
        return false;
    }

释放锁:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//每次unlock,state减1
            if (Thread.currentThread() != getExclusiveOwnerThread())//判断是不是锁的持有线程
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//state为0表示该锁没有被持有
                free = true;
                setExclusiveOwnerThread(null);//将锁的持有者置null
            }
            setState(c);
            return free;
        }

唤醒后继者:

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        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)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

2.2.读写锁

读写锁是一个实现读写互斥的锁,读写锁包含一个读锁、一个写锁:

public interface ReadWriteLock{
    Lock readLock();
    Lock writeLock();
}

读写锁的使用就是直接调用对应锁进行锁定和解锁:

ReadWriteLock rwLock=new ReetrantReadWriteLock();
Lock rLock=rwLock.readLock();
rLock.lock();
rLock.unLock();
Lock wLock=rwLock.writeLock();
wLock.lock();
wLock.unLock();

读写锁的Sync内部类对读锁和写锁采用同一个int型的信号量的高16位和低16位分别表示读写锁的状态和重入次数,这样一次CAS就能统一处理进行读写互斥操作:

abstract static class Sync extends AbstractQueuedSynchronizer {
        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}

2.3.Condition

2.3.1.概论

condition用于更加细粒度的控制锁上面的线程阻塞、唤醒。

以下以一个经典的生产、消费者问题为例:

队列空的时候进来的消费者线程阻塞,有数据放进来后唤醒阻塞的消费者线程。

队列满的时候进来的生产者线程阻塞,有空位后唤醒阻塞的生产者线程。

锁粒度的实现:

public void enqueue(){
    synchronized(queue){
        while(queue.full()){
            queue.wait();
        }
        //入队列
        ......
        //通知消费者,队列中有数据了
        queue.notify();
    }
}
​
public void dequeue(){
    synchronized(queue){
        while(queue.empty()){
            queue.wait();
        }
        //出队列
        ......
        //通知生产者,队列中有空位了,可以继续放数据
        queue.notify();
    }
}

可以发现,唤醒的时候把阻塞的生产消费线程一起唤醒了。

条件粒度的实现:

private final Lock lock = new ReentrantLock();
private final Condition notFull  = lock.newCondition(); // 用于等待队列不满
private final Condition notEmpty = lock.newCondition(); // 用于等待队列非空

public void enqueue(Object item) {
    try {
        while (queue.isFull()) {
            notFull.await(); // 等待队列不满
        }
        // 入队列操作
        // ...
        
        // 入队后,通知等待的消费者
        notEmpty.signal();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 保持中断状态
        // 处理中断逻辑
    } finally {
        queue.unlock();
    }
}

public void dequeue() {
    try {
        while (queue.isEmpty()) {
            notEmpty.await(); // 等待队列非空
        }
        // 出队列操作
        // ...
        
        // 出队后,通知等待的生产者
        notFull.signal();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 保持中断状态
        // 处理中断逻辑
    } finally {
        queue.unlock();
    }
}

2.3.2.底层实现

Condition由Lock产生,因此Lock中持有Condition:

public interface Lock {
    ......
    Condition newCondition();
}

承担功能的其实就是Syn中的ConditionObject,也就是AQS中的ConditionObject:

final ConditionObject newCondition() {
            return new ConditionObject(this);
        }

一个Condition上面阻塞着多个线程,所以每个Condition内部都有一个队列,用来记录阻塞在这个condition上面的线程,这个队列其实也是AQS实现的,AQS中除了实现一个以Node为节点的队列,还实现了一个以ConditionObject为节点的队列:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
        public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        private transient Node firstWaiter;
        private transient Node lastWaiter;
        ......
        }
    }

Condition是个接口,定义了一系列条件操作:

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long var1) throws InterruptedException;
    boolean await(long var1, TimeUnit var3) throws InterruptedException;
    boolean awaitUntil(Date var1) throws InterruptedException;
    void signal();
    void signalAll();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/767540.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Google 发布了最新的开源大模型 Gemma 2,本地快速部署和体验

Gemma 2 是 Google 最新发布的开源大语言模型。它有两种规模&#xff1a;90 亿&#xff08;9B&#xff09;参数和 270 亿&#xff08;27B&#xff09;参数&#xff0c;分别具有基础&#xff08;预训练&#xff09;和指令调优版本&#xff0c;拥有 8K Tokens 的上下文长度&#…

Akamai+Noname强强联合 | API安全再加强

最近&#xff0c;Akamai正式完成了对Noname Security的收购。本文我们将向大家介绍&#xff0c;经过本次收购后&#xff0c;Akamai在保护API安全性方面的后续计划和未来愿景。 Noname Security是市场上领先的API安全供应商之一&#xff0c;此次收购将让Akamai能更好地满足日益增…

【C语言】extern 关键字

在C语言中&#xff0c;extern关键字用于声明一个变量或函数是定义在另一个文件中的。它使得在多个文件之间共享变量或函数成为可能。extern关键字常见于大型项目中&#xff0c;通常用于声明全局变量或函数&#xff0c;这些变量或函数的定义位于其他文件中。 基本用法 变量声明…

智能语音门锁:置入NV170D语音芯片ic 打造便捷生活新体验

一、智能门锁语音芯片开发背景 随着科技的飞速发展&#xff0c;传统门锁的局限性日益凸显&#xff0c;无法满足现代人对高效、安全生活的需求。在这样的时代背景下&#xff0c;智能门锁应运而生&#xff0c;它不仅继承了传统门锁的基本功能&#xff0c;更通过融入先进的科技元素…

YOLOv8改进 | 卷积模块 | SAConv可切换空洞卷积

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

市场规模5万亿,护理员缺口550万,商业护理企业如何解决服务供给难题?

干货抢先看 1. 据统计&#xff0c;我国失能、半失能老人数量约4400万&#xff0c;商业护理服务市场规模达5万亿。然而&#xff0c;当前养老护理员缺口巨大&#xff0c;人员的供需不匹配是很多养老服务企业需要克服的难题。 2. 当前居家护理服务的主要市场参与者分为两类&…

儿童无语言是否等同于自闭症?全面解析与认识自闭症

在探讨自闭症与儿童语言发展之间的关系时&#xff0c;我们首先需要明确的是&#xff0c;自闭症并非单一由语言缺失所定义&#xff0c;而是一个复杂的神经发育障碍&#xff0c;其核心特征包括社交互动和沟通能力的显著受损&#xff0c;以及重复、刻板的行为模式、兴趣或活动。 …

基于SpringBoot高校体育运动会管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

【HICE】基于httpd下的web服务器搭建

1.下载httpd&#xff1a; dnf install httpd -y 2.进入httpd中&#xff1a; cd /etc/httpd cd conf.d 3.编辑一个新的vhost.conf 4.重启httpd服务 systemctl restart httpd 5.关闭防火墙 systemctl stop firewalld setenforce 0 6.文本写入&#xff08;网页编辑&…

第6章:结构化开发方法

第6章&#xff1a;结构化开发方法 系统设计基本原理 1、抽象 抽象是一种设计技术&#xff0c;重点说明一个实体的本质方面&#xff0c;而忽略或者掩盖不是很重要或非本质的方面。 模块化 模块化是指将一个待开发的软件分解成若干个小的、简单的部分一模块&#xff0c;每个模…

尝试修改苍穹外卖为”李小罗餐厅“

学习苍穹外卖后&#xff0c;将其修改为自己所需要的项目&#xff0c;也是对苍穹外卖项目的加深理解 对项目之间的连接等关系进一步清晰&#xff0c;那么便开始吧 d1_开始修改 修改名字为”李小罗餐厅“ src\views\login\index.vue src\router.ts 结果展示 修改进来之后的展示…

从0到1手写vue源码

模版引擎 数组join法(字符串) es6反引号法(模版字符串换行) mustache (小胡子) 引入mustache 模版引擎的使用 mustache.render(templatestr,data)

【linux学习---1】点亮一个LED---驱动一个GPIO

文章目录 1、原理图找对应引脚2、IO复用3、IO配置4、GPIO配置5、GPIO时钟使能6、总结 1、原理图找对应引脚 从上图 可以看出&#xff0c; 蜂鸣器 接到了 BEEP 上&#xff0c; BEEP 就是 GPIO5_IO05 2、IO复用 查找IMX6UL参考手册 和 STM32一样&#xff0c;如果某个 IO 要作为…

ctfshow sql注入

开启其他注入 web221 limit注入 给出查询语句 以及过滤逻辑为空 获取数据库名即可 limit 用于控制返回结果行数 limit后面似乎只能跟PROCEDURE ANALYSE( ) 函数了 PROCEDURE ANALYSE( ) 函数用于分析查询结果的函数 参数是用来控制函数的 这个参数的位置 可以放入报错函数 原…

centos7.9 python3环境(virtualenv)搭建及所遇错误

人望山&#xff0c;鱼窥荷&#xff0c;真正喜欢想要的&#xff0c;没有一样可以轻易得到。 目录 # 1. 解决版本冲突问题--建议不要跳过(一定要查看软链接是否链接正确) # 2. python3(virtualenv)环境搭建 # 3. virtualenv常用命令 # 4. 所遇错误解析 ## 4.1 遇到 No modul…

关于python编程从入门到实践书中的外星人项目的 if event.key == pygame.K_q: sys.exit()失败问题

按q没有退出程序。原因是输入法中英文问题。 本人默认输入法是搜狗&#xff0c;其他的输入法如微软拼音等都行&#xff0c;但是注意运行的时候切换为英文。千万记得运行时不是中&#xff0c;而是英&#xff0c;按q才能退出

【数据结构】堆栈

目录 一、堆栈的基本概念 1.1 堆栈定义 1.2 堆栈操作 1.3 堆栈应用 二、顺序栈 2.1 定义 2.2 操作 2.3 C语言实现 三、共享栈 3.1 定义 3.2 操作 3.3 C语言实现 四、链式栈 4.1 定义 4.2 操作 4.3 C语言实现 五、总结 堆栈(Stack)重要的数据结构,它…

Python--线程基础

相关概念 线程是"轻量级进程",是计算机中CPU进行任务调度的最小单位。 线程属于进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,且至少有一个线程。 每个进程开始的创建的时候,都会随之创建一个主线程。 进程负责分配和隔离资源(CPU, 内存…

机器学习辅助的乙醇浓度检测

目录 1.为什么要机器学习 2. 神经网络一般组成 3.BP神经网络工作过程 4.评价指标 5.实操代码 1.为什么要用机器学习 人工分析大量的谐振模式&#xff0c;建立各种WGM的响应与未知目标之间的关系&#xff0c;是一个很大的挑战。机器学习(ML)能够自行识别全谱的全部特征。作为…

【Python】Python中的常量与变量

常量与变量 导读一、新建项目二、常量2.1 字面常量2.2 特殊常量 三、变量3.1 变量的定义3.2 变量的命名3.2.1 关键字 结语 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 在上一篇内容中我们详细介绍了Python环境的搭建过程&#xff0c;…