并发编程4 - 线程状态、死锁及ReentrantLock

2023-11-19

一. 再述线程状态转换

在这里插入图片描述
情况1:New ——》RUNNABLE
当调用 t.start() 方法时

情况2:RUNNABLE 《——》WAITING
线程 synchronized(obj) 获得锁以后,调用 obj.wait 方法,状态将从 RUNNABLE ——》WAITING;调用 notify、notifyAll、interrupt 方法,如果:
- 竞争锁失败,线程从 WAITING ——》 BLOCKED
- 竞争锁成功,线程从 WAITING ——》RUNNABLE

注意 BLOCKED 状态与 WAITING 状态的区别:
waiting:主动为之,wait()方法释放cpu执行权和释放锁进入等待队列,需要notify()唤醒进入同步队列竞争锁;
blocked:被动的,在竞争锁的时候失败,被阻塞,在同步队列里继续竞争锁。

情况3:RUNNABLE 《——》WAITING

  • 当当前线程调用 t.join() 方法时,当前线程从 RUNNABLE ——》WAITING。
  • t 线程运行结束,或调用了当前线程的 interrupt(),当前线程从 WAITING ——》RUNNABLE 。

情况4:RUNNABLE 《——》WAITING

  • 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE ——》WAITING;
  • 调用 LockSupport.unpark(t) 或调用了线程的 interrupt(),会让目标线程从 WAITING ——》RUNNABLE 。

情况5/6/7/8:RUNNABLE 《——》TIMED_WAITING

调用 obj.wait(n)、t.join(n)、Thread.sleep(n)、LockSupport.parkNanos(n)、LockSupport.parkUntil(n) 时,线程从 RUNNABLE ——》TIMED_WAITING。

注意:调用sleep(long)方法不会释放锁,时间到了自己返回原状态。

情况9:RUNNABLE 《——》BLOCKED

  • t 线程用 synchronized(obj) 竞争对象锁失败时,从 RUNNABLE ——》BLOCKED;
  • 获得锁的线程执行完毕后,会唤醒所有阻塞的线程重新竞争,竞争成功的从 BLOCKED ——》RUNNABLE

情况10:RUNNABLE ——》TERMINATED
线程中的代码全部执行完毕,RUNNABLE ——》TERMINATED。

二. 多把锁与线程活跃性问题

1. 多把锁

当两个业务完全不相干时,可以选择更加细粒度的锁,增加程序的并发性。

class MultiLock {
    //两个对象锁
    private Object bedRoom = new Object();
    private Object studyRoom = new Object();
    
    public void sleep() {
        //不再锁住 this 对象,而是更加细粒度的锁
        synchronized (bedRoom) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public void study() {
        synchronized (studyRoom) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. 活跃性

(1)死锁

但是当一个程序中存在多把锁,而一个线程需要同时获得多把锁时,就容易发生死锁。

public class MultiLockTest {
    final static Logger logger = LoggerFactory.getLogger(MultiLockTest.class);
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                try {
                    logger.info("lock 1 ……");
                    Thread.sleep(1000);
                    synchronized (lock2) {
                        logger.info("lock 2 ……");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                try {
                    logger.info("lock 2 ……");
                    Thread.sleep(2000);
                    synchronized (lock1) {
                        logger.info("lock 1 ……");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

在这里插入图片描述
(2) 定位死锁

方式1:jps + jstack
在这里插入图片描述在这里插入图片描述
方式2:jconsole

连接——》线程——》检测死锁
在这里插入图片描述
(3)哲学家就餐问题

在这里插入图片描述
程序模拟死锁问题:

筷子类

class Chopstick {
    private String name;
    public Chopstick(String name) {
        this.name = name;
    }
}

哲学家类

class Philosopher extends Thread {
    Logger logger = LoggerFactory.getLogger(Philosopher.class);
    //模拟左右两根筷子
    private Chopstick left;
    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while(true) {
        	//需要两根筷子才能吃饭
            synchronized (left) {
                synchronized (right) {
                    logger.info("eating ……");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

测试类

public class PhilosopherTest {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("c1");
        Chopstick c2 = new Chopstick("c2");
        Chopstick c3 = new Chopstick("c3");
        Chopstick c4 = new Chopstick("c4");
        Chopstick c5 = new Chopstick("c5");

        new Philosopher("p1", c1, c2).start();
        new Philosopher("p2", c2, c3).start();
        new Philosopher("p3", c3, c4).start();
        new Philosopher("p4", c4, c5).start();
        new Philosopher("p5", c5, c1).start();

    }
}

在这里插入图片描述
jconsole检测结果,五个线程均陷入死锁:
在这里插入图片描述
(4)活锁

当两个线程都在改变对方的运行结束条件时会发生活锁。
示例:

public class LiveLockTest {
    static int count = 10;
    private static final Logger logger = LoggerFactory.getLogger(LiveLockTest.class);

    public static void main(String[] args) {
        new Thread(() -> {
            while (count > 0) {
                count--;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("count: " + count);
            }
        }, "t1").start();
        new Thread(() -> {
            while (count < 20) {
                count++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("count: " + count);
            }
        }, "t2").start();

    }
}

在这里插入图片描述

与死锁的不同:死锁是两个线程都陷入了阻塞状态,而活锁是两个线程都在运行,只是无法结束。
解决活锁问题,可以使两个线程交错运行,或者增加随机睡眠时间。

Thread.sleep(new Random().nextInt(1000));

(5)饥饿

饥饿:一个线程由于优先级太低,始终得不到CPU调度执行,也不能够结束。

比如之前的哲学家就餐问题,为避免死锁问题,我们可以通过采用顺序获取锁的方式解决:

new Philosopher("p1", c1, c2).start();
new Philosopher("p2", c2, c3).start();
new Philosopher("p3", c3, c4).start();
new Philosopher("p4", c4, c5).start();
new Philosopher("p5", c1, c5).start(); //获取锁的方式改为 c1, c5

但是日志显示,P4和P3线程获取锁的几率较高,而P5线程几乎获取不到锁,这就是饥饿现象:
在这里插入图片描述

三. ReEntrantLock

1. 基本用法

//获取锁,只有获取到了锁后面的代码才会继续执行,否则将一直阻塞在这里
reentrantLock.lock();
try {
	// 临界区 
} finally {
	// 释放锁
	reentrantLock.unlock();
}

2. 可重入

可重入是指同一个线程如果已经获得了一把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获取锁时,自己也会被锁挡住。

synchronized 也是可重入锁。

public class ReEntrantLockTest {
    private static final Logger logger = LoggerFactory.getLogger(ParkTest.class);
    public static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            logger.info("enter main……");
            m1();
        } finally {
            lock.unlock();
        }
    }

    public static void m1() {
        lock.lock(); //可重入
        try {
            logger.info("enter m1……");
        } finally {
            lock.unlock();
        }
    }
}

3. 可打断

可打断表明可以打断一直处于等待锁状态的线程,避免死锁。

synchronized 是不可打断锁,因为interrupt() 方法只是将打断标记设置为 true,当线程未获取到锁处于阻塞状态下时,并没有显式抛出异常。而处于WAITING 状态下的线程可被打断是因为 wait、sleep、join 这些方法会检查打断标记,并抛出InterruptedException异常。为什么synchronized不可打断

ReentrantLock的 lock 方法也是不可打断的,而 lockInterruptibly() 方法 是可打断的。
示例:

public class ReEntrantLockTest {
    private static final Logger logger = LoggerFactory.getLogger(ParkTest.class);
    public static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                logger.info("t1线程尝试获取锁……");
                lock.lockInterruptibly();
                logger.info("t1获取到锁……");
                lock.unlock();
            } catch (InterruptedException e) {
                logger.info("t1线程没有获取到锁……");
                e.printStackTrace();
            }
        });

        lock.lock();
        logger.info("主线程获取到锁……");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

在这里插入图片描述
查看 lockInterruptibly() 方法的源码发现,它其实也是通过检查打断标记抛出InterruptedException异常,来实现可打断的功能:

lockInterruptibly 源码:在这里插入图片描述
在这里插入图片描述

4. 锁超时

tryLock()tryLock(long timeout, TimeUnit unit) 方法允许线程自己设置锁等待时间,前者没有获取到锁立即返回,后者会等待一定的时间,超过时间没有获取到锁则返回。

public class TryLockTest {
    private static final Logger logger = LoggerFactory.getLogger(TryLockTest.class);
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            logger.info("尝试获取锁");
            try {
                if (! lock.tryLock(2, TimeUnit.SECONDS)) {
                    logger.info("没有获取到锁");
                    return; //没获取到锁需要返回
                }
            } catch (InterruptedException e) { //带参数的tryLock方法也允许被打断
                e.printStackTrace();
                logger.info("没有获取到锁");
                return;
            }
            //获取到了锁需要执行的代码
            try {
                logger.info("获取到了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        logger.info("获取了锁");
        lock.lock();
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("释放了锁");
        lock.unlock();
    }
}

使用 tryLock() 解决哲学家就餐问题:

由于不能再使用 synchronized 锁住筷子对象,则筷子对象本身就需要具有锁特性。因此,这里将筷子类继承 ReentrantLock 类。

class Chopstick extends ReentrantLock {
    private String name;
    public Chopstick(String name) {
        this.name = name;
    }
}

将 synchronized 换成 tryLock 或者 tryLock(long timeout, TimeUnit unit) :

class Philosopher extends Thread {
    Logger logger = LoggerFactory.getLogger(Philosopher.class);
    private Chopstick left;
    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while(true) {
        	//将synchronized换成 tryLock
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            logger.info("eating ……");
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                	//解决死锁的关键就在于右筷子拿不到时会释放左筷子
                    left.unlock();
                }
            }
        }
    }
}

在这里插入图片描述

5. 公平锁

synchronized 锁是不公平锁,即当锁被释放时,阻塞队列里的线程会同时去争抢锁,而不是按照进入阻塞队列的顺序获取锁。

ReentrantLock 默认是不公平锁,可以通过改变其构造方法将其设置为公平锁:

ReentrantLock lock = new ReentrantLock(true);

公平锁可以用来解决饥饿问题,但是会降低并发度,一般无需设置。

6. 条件变量

与 synchronized 的 wait/notify 类似的概念,当条件不满足时,为了不阻塞其他线程用锁,可以进入 waitSet 等待。

ReentrantLock 的条件变量更强大的地方在于,它支持多个条件变量,即等待不同条件的线程可以进入不同的等待室。

使用流程:

  • await 前需要获得锁;
  • await 执行后会释放锁,进入 conditionObject 等待;
  • await 的线程被唤醒(或打断、或超时)后,需要重新竞争锁;
  • 竞争锁成功后,从await 后继续执行。

await / signal 使用示例:

public class ReEntrantLockTest {
    private static final Logger logger = LoggerFactory.getLogger(ParkTest.class);
    public static ReentrantLock lock = new ReentrantLock();
    static boolean condition1 = false;
    static boolean condition2 = false;

    public static void main(String[] args) throws Exception {

        Condition waitSet1 = lock.newCondition();
        Condition waitSet2 = lock.newCondition();

        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                while (!condition1) {
                    try {
                        logger.info("条件1不满足,进入等待");
                        waitSet1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                logger.info("t1 开始执行");
            } finally {
                lock.unlock();
            }

        }, "t1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                while (!condition2) {
                    try {
                        logger.info("条件2不满足,进入等待");
                        waitSet2.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                logger.info("t2 开始执行");
            } finally {
                lock.unlock();
            }
        }, "t2");

        t1.start();
        t2.start();
        Thread.sleep(2000);
        lock.lock();
        try {
            logger.info("通知t1执行……");
            condition1 = true;
            waitSet1.signal();

            logger.info("通知t2执行……");
            condition2 = true;
            waitSet2.signal();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发编程4 - 线程状态、死锁及ReentrantLock 的相关文章

  • 方法锁,对象锁,类锁的区别和用法

    在java编程中 经常需要用到同步 而用得最多的也许是synchronized关键字了 下面看看这个关键字的用法 因为synchronized关键字涉及到锁的概念 所以先来了解一些相关的锁知识 java的内置锁 每个java对象都可以用做一
  • 并发编程集合

    转载自郑金维老师 一 synchronized 一 原子性 有序性 可见性 1 1 原子性 数据库的事务 ACID A 原子性 事务是一个最小的执行的单位 一次事务的多次操作要么都成功 要么都失败 并发编程的原子性 一个或多个指令在CPU执
  • 并发编程系列之Exchanger

    前言 上面我们介绍了信号量 再来说说交换者 这个东西用的不是很多 所以一般也不被经常关注 但是我们还是最好了解下 下面我将从什么是Exchanger以及如何使用Exchanger两个方面谈谈这个用于线程间协调的工具类 什么是Exchange
  • hystrix线程池隔离的原理与验证

    引子 幸福很简单 今天项目半年规划被通过 终于可以早点下班 先坐公交 全程开着灯 买了了几天的书竟然有时间看了 半小时后 公交到站 换乘大巴车 车还等着上人的功夫 有昏暗的灯光 可以继续看会儿书 过会儿车跑起来了 灯关了 我合上书 头靠着车
  • MPI测试程序

    include
  • 【多线程】三种实现方案

    目录 1 多线程中的并发和并行概念 2 多线程中的进程和线程概念 3 多线程的实现方案 3 1 方式1 继承Thread类的方式进行实现 3 2 方式2 实现Runnable接口 3 3 方式3 Callble和Future 可以获取返回结
  • 并发编程JMM系列之重排序和顺序一致性

    前言 昨天我们接触到了什么是Java内存模型以及两种Java并发模型 并对JMM有了一些初步的认识和了解 我们在上节有提到JMM的重排序规则 但是讲的不详细 今天我们再重点聊下重排序这个东西 以及顺序一致性内存模型 OK 开始我们今天的并发
  • 并发编程系列之线程简介

    前言 前几天我们把Java内存模型介绍了下 大家对JMM也有所认识了 从今天我们就开始走进一个我们天天挂在嘴边 听在耳边的东西 线程 对于线程相信大家都不会陌生 当然也有很多小伙伴在开发中或多或少的使用到线程 即使你没有使用过 但是并不代表
  • JUC常用到的类

    JUC java util concurrent 并发包中包含了许多并发编程中需要用到的类 锁 如ReentratLock ReadWriteLock ReentrantLock重入锁 可以替代synchronized使用 并且有更多强大的
  • <并发编程>学习笔记------(一) 并发相关理论

    前面 并发编程可以总结为三个核心问题 分工指的是如何高效地拆解任务并分配给线程 同步指的是线程之间如何协作 互斥则是保证同一时刻只允许一个线程访问共享资源 并发相关理论 可见性 原子性和有序性 核心矛盾 CPU 内存 I O 设备的速度差异
  • 上下文切换理解以及减少方法

    并发编程面临着上下文切换 死锁等问题 尤其在少量数据的情况下 并发可能因为线程的创建和上下文切换的开销等问题 甚至比串行执行的速度更慢 文章目录 上下文切换定义 例子理解 减少上下文切换的方法 无锁并发编程 CAS算法 使用最少线程 协程
  • ReentrantLock实现PV操作-模拟多线程竞争数据库连接池资源场景

    使用ReentrantLock Condition模拟PV操作 实现多线程竞争数据库连接池资源 资源耗尽后阻塞等待 归还资源后唤醒阻塞线程的场景 代码中为10个线程竞争5个数据库连接资源 ConnectionPool class 连接池 C
  • shell编程笔记3--shell并发

    shell编程笔记3 shell并发 shell编程笔记3 shell并发 介绍 并发方法 1 简单后台方式 2 普通控制并发量方式 3 通过管道控制并发量 参考文献 shell编程笔记3 shell并发 介绍 在shell中适当使用并发功
  • Java并发编程实战——并发容器之ConcurrentHashMap(JDK 1.8版本)

    文章目录 ConcurrentHashmap简介 从关键属性及类上来看ConcurrentHashMap的结构 put 方法管中窥豹 CAS关键操作 ConcurrentHashmap简介 在使用HashMap时在多线程情况下扩容会出现CP
  • 测试开发工程师面试总结(一)——Java基础篇

    本文面向对象 测试开发工程师 服务端自动化方向 随手百度一下都能找到 岗位面试总结 但是有关测开岗位的面试总结却寥寥无几 总体原因可能是这两个 1 测试行业整体水平参差不齐 导致不同公司面试的问题不能抽象出来写概览 2 很多做测开的人可能内
  • Lock锁

    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作 它们允许更灵活的结构化 可能具有完全不同的属性 并且可以支持多个相关联的对象Condition 1 传统的synchronized package cn d
  • Java 多线程模式 —— Guarded Suspension 模式

    Part1Guarded Suspension 模式的介绍 我们只从字面上看 Guarded Suspension 是受保护暂停的意思 1Guarded Suspension 模式 在实际的并发编程中 Guarded Suspension
  • Java并发编程之设计模式

    同步模式之保护性暂停 1 定义 即 Guarded Suspension 用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程 让他们关联同一个 GuardedObject 如果有结果不断从一个线程到另一个线
  • 并发编程 (6)一不小心就死锁了,怎么办?

    在上一篇文章中 我们用 Account class 作为互斥锁 来解决银行业务里面的转账问题 虽然这个方案不存在并发问题 但是所有账户的转账操作都是串行的 例如账户 A 转账户 B 账户 C 转账户 D 这两个转账操作现实世界里是可以并行的
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可

随机推荐

  • hbuilder 微信支付成功 需要通知服务器吗,整合Hbuilder,实现app的微信支付与支付宝支付...

    1 对于使用Hbuilder开发app对接的移动支付 相信关于这方面的文档有不少 本人今天简单说一下本身在作的时候碰见的坑 html 1 1 在app客户端 使用Hbuilder写客户端代码时候 必定要注意ios 这两个的顺序 要否则会出现
  • 计算机网络——数字数据的数字编码

    计算机网络 数字数据的数字编码 数字数据的数字编码就是如何把数字数据用物理信号的波形表示 即用高低电平表示二进制 1 不归零码 正电平代表1 负电平代表0 2 归零码 正脉冲代表1 负脉冲代表0 3 曼彻斯特编码 位周期中心的上跳代表0 周
  • 吴恩达与OpenAI官方合作的ChatGPT提示工程课程笔记

    吴恩达与OpenAI官方合作的ChatGPT提示工程课程笔记 下述代码均在煮皮特上运行喔 LLMs large language models Base LLM 基于文本训练数据来预测做 文字接龙 Instruction Tuned LLM
  • git小技巧:git blame && git show 查看某一行代码的修改历史

    先查看某行代码由谁写的 在哪个commit中提交的 git blame file name 其显示格式为 commit ID 代码提交作者 提交时间 代码位于文件中的行数 实际代码 类似于下面这样 f604879e yingyinl 201
  • 某某星图sign参数解密分析

    大家好 我是TheWeiJun 欢迎来到我的公众号 今天给大家带来星图sign参数的解密分析 希望大家能够喜欢 如果你觉得我的文章内容有价值 记得点赞 关注 特别声明 本公众号文章只作为学术研究 不用于其他用途 逆向与爬虫的故事 公众号 专
  • 主线程退出后,子线程会不会退出

    额 好吧 这是个标题党 其实所有的线程都是平级的 根本不存在主线程和子线程 下文所述为了方便 将在main函数中的线程看做主线程 其它线程看成子线程 特此说明 先考虑以下代码 include
  • 基于深度学习的正常衰老和痴呆症中的脑龄预测

    大脑衰老过程中会出现一系列功能和结构的改变 阿尔茨海默病 AD 作为一种典型的神经退行性疾病 与大脑加速衰老有关联 在本研究中 我们利用大量的氟脱氧葡萄糖正电子发射断层扫描 FDG PET 和结构磁共振成像 MRI 数据 构建了一个基于深度
  • 期货开户顺大市而逆小市

    期货的行情 有人愿意以更高的价来买入 就会涨 有人买意以更低的价格卖出 就会跌 现货市场上 一个馒头5角钱的时候 在期货市场上 如果有很多人争着买 这个馒头可能会涨到5块 或者50块 也是可能的 在这个馒头5块钱一个的时候 你感觉这个馒头太
  • Servlet+JDBC实战开发书店项目讲解第五篇:购物车实现

    Servlet JDBC实战开发书店项目讲解第五篇 购物车实现 引言 在之前的几篇博客中 我们讲解了如何使用Servlet和JDBC开发一个简单的书店管理系统 在本文中 我们将深入探讨购物车的实现 这是一个关键功能 允许用户将所需图书添加到
  • Java的多态特性

    学习笔记 多态 简单说 就是一个对象对应着不同类型 多态在代码中的体现 父类或者接口的引用指向其子类的对象 多态的好处 提高可维护性 由多态前提所保证 提高了代码的扩展性 多态的弊端 无法直接访问子类特有的成员 也就是说前期定义的内容不能使
  • [C++基础]-stack和queue

    前言 作者 小蜗牛向前冲 名言 我可以接受失败 但我不能接受放弃 如果觉的博主的文章还不错的话 还请点赞 收藏 关注 支持博主 如果发现有问题的地方欢迎 大家在评论区指正 目录 一 stack的基本知识 1 什么是栈 2 栈的基本使用 3
  • Python 使用execjs调用网页js 进行数据加密

    最近做一个数据采集项目的时候需要自动采集网站的招投标数据 随便打开一个网站 打开开发者模式 输入关键词 点击搜索 获得以下内容 可以看到请求链接和请求类型 请求类型Content Type 是application x www form u
  • maven 项目导入junit问题

    maven项目无法导入 import org junit Test import org junit runner RunWith 问题 1 检查 Maven Dependencies中Junit的jar中是否有此类 如果没有 说明pom
  • 判断一个IP地址是不是单播地址

    1 组播地址 2 单播地址 1 2
  • C++_生成随机字符串

    include
  • Vite配置跨域代理

    Vite 配置跨域代理 修改vite config js文件 import defineConfig from vite import react from vitejs plugin react https vitejs dev conf
  • Xilinx AXI-memory接口 转 AXI-stream 接口(含源码)

    AXI memory接口 转 AXI stream 接口 AXI memory接口介绍 具体详情可以查看源码 AXI memory接口介绍 从图中我们可以看出memory接口有5个通道 分别是读地址通道 写地址通道 写响应通道 读数据通道
  • 华为OD两轮技术面试

    华为OD面试 1性格测试 选积极向上的选项 注意 性格测试也会挂人 我一个朋友性格测试就没过 2机试 一道变成题目 1h 用例60 通过即可 任给一个数组 元素有20M 1T 300G之类的 其中1T 1000G 1G 1000M 按从小到
  • 数据库事务锁详解

    前言 上篇说到数据库事务中的特性ACID和4个隔离级别 今儿就来看一下事务中的锁 MySQL中的锁 锁是MySQL在服务器层和存储引擎层的并发控制 锁可以保证数据并发访问的一致性 有效性 锁冲突也是影响数据库并发访问性能的一个重要因素 My
  • 并发编程4 - 线程状态、死锁及ReentrantLock

    文章目录 一 再述线程状态转换 二 多把锁与线程活跃性问题 1 多把锁 2 活跃性 三 ReEntrantLock 1 基本用法 2 可重入 3 可打断 4 锁超时 5 公平锁 6 条件变量 一 再述线程状态转换 情况1 New RUNNA