JUC Lock 锁入门

news2025/4/21 6:23:51

文章目录

  • 死锁(Deadlock)
    • 通过 Visualvm 等工具排查死锁
  • 活锁
  • park & unpark
    • 与 wait & notify 的区别
    • park & unpark 实现:点外卖
  • Lock 对象
  • ReentrantLock 可重入锁
    • 可重入
    • lockInterruptibly 方法上锁(可打断)
    • tryLock方法获取锁(锁超时)
  • 公平锁
  • 条件变量 Condition

死锁(Deadlock)

两个或多个线程互相等待对方释放资源,从而导致它们永远无法继续执行。死锁通常涉及多个锁,线程之间在等待对方释放锁时都会被阻塞
比如:t1线程已经获得A锁,进而请求B锁,t2线程已经获得B锁,进而请求A锁。导致两个线程永久阻塞。

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DeadlockTest {

    public static void main(String[] args) {

        final Object A = new Object();
        final Object B = new Object();

        new Thread(()->{

            synchronized (A){
                log.debug("成功获取 A 锁准备获取 B 锁");
                synchronized (B){
                    log.debug("执行成功");
                }
            }

        },"t1").start();


        new Thread(()->{

            synchronized (B){
                log.debug("成功获取 B 锁准备获取 A 锁");
                synchronized (A){
                    log.debug("执行成功");
                }
            }

        },"t2").start();

    }

}

通过 Visualvm 等工具排查死锁

在这里插入图片描述
在这里插入图片描述

可使用顺序加锁的方式来解决此死锁问题(不要交错获取锁,不然容易形成死锁)。

注:在 wait 或 join 方法上无限等待的线程,既不是死锁也不是活锁,因为我们可以通过其他线程调用 interrupt 方法来打断此无限等待的情况。如下面的示例:

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class WaitTest {

    private static Thread t1,t2;
    public static void main(String[] args) {
        Object obj = new Object();
        // 线程t1和t2的结束条件都是等对方先执行
        t1 = new Thread(() -> {
            synchronized (obj) {
                try {
                    log.debug("t1 准备让 t2 执行");
                    t2.join();
                    log.debug("t1 执行完毕");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t1");

        t2 = new Thread(() -> {
            synchronized (obj) {
                try {
                    log.debug("t2 准备让 t1 执行");
                    t1.join();
                    log.debug("t2 执行完毕");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t2");

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

//        t1.interrupt(); // 调用 interrupt 方法可打断

    }

}

活锁

线程的状态不断的被改变(自身或其他线程修改),导致该线程一直在执行且无法结束。

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class AliveLockTest {

    private static Integer count = 100;

    public static void main(String[] args) {

        // count 大于 0 执行线程 t1,并 count--
        // count 小于 50 执行线程 t2,并 count++
        new Thread(()->{
            synchronized (count) {
                while (count > 0) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    count--;
                }
            }
            log.debug("线程 t1 执行结束");
        },"t1").start();

        new Thread(()->{
            synchronized (count) {
                while (count < 50) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    count++;
                }
            }
            log.debug("线程 t2 执行结束");
        },"t2").start();

    }

}

死锁和活锁都会导致线程无法停止,但死锁是程序被阻塞不执行,活锁是程序一直执行不结束。

活锁解决通常让其中一个线程先执行完,则另一个线程就能执行完了。

注:此示例如果不加 sleep 有很大概率可以执行结束,但其一定会经历大量的循环执行

  • 饥饿(Starvation)

某些线程一直无法获得所需的资源(由于资源抢占的不公平性),导致一直无法执行。

解决线程饥饿问题通常采用公平锁的方式来解决。

park & unpark

  • LockSupport.park():在某一个线程中调用,表示暂停当前线程。
  • LockSupport.unpark(Thread thread):恢复已暂停的线程

park & unpark 方法底层都是使用的 sun.misc.Unsafe UNSAFE 对象,对应为 native 方法。

与 wait & notify 的区别

  • wait & notify 必须与 synchronized 关键字一起使用,park & unpark 没有这个限制(这也导致相应的代码块是没有同步的)
  • notify 唤醒的线程是随机的,而 unpark 可以指定要唤醒的线程
  • unpark 可以在 park 之前调用,且会生效,后调用的 park 方法也会被取消暂停。而 notify 不能先于 wait 方法调用。

park & unpark 实现:点外卖

import java.util.concurrent.locks.LockSupport;

/**
 * 点外卖:
 * 线程1:商家
 * 线程2:买家
 * 线程3:骑手
 * 使用 park & unpark 实现
 */
public class ParkUnparkTest {

    /**
     * 0:未点餐
     * 1:已点餐,未制作
     * 2:制作完成,骑手未送货
     * 3:骑手送货成功,可以开始干饭了
     */
    private static int state = 0;

    private static Thread t1,t2,t3;

    public static void main(String[] args) {



        // 商家
        t1 = new Thread(()->{
            while (state < 1){// 没人点餐就等待(需要循环等待,使用 if 会导致虚假唤醒问题(notifyAll 同时唤醒了骑手和买家,但是买家抢到了锁))
                System.out.println(Thread.currentThread().getName()+":没人点餐,等待中");
                LockSupport.park();
            }
            // 有人点餐就制作
            System.out.println(Thread.currentThread().getName()+":已接单,制作中");
            try {
                // 模拟商家制作时间
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+":制作成功");
            state = 2;
            LockSupport.unpark(t3);// 通知骑手接单(这里可以精确指定骑手线程)
        },"商家");

        // 买家
        t2 = new Thread(()->{
            long start = System.currentTimeMillis();
            if(state == 0){
                state = 1; // 下单
                System.out.println(Thread.currentThread().getName()+":下单成功,等待送餐");
                LockSupport.unpark(t1);// 通知卖家线程制作
            }
            LockSupport.park();// 等待送餐
            while (state < 3){// 没有送到,就等待
                System.out.println(Thread.currentThread().getName()+":外卖没送到,等待中");
                LockSupport.park();// 等待送餐
            }
            // 送到了,就开始干饭
            System.out.println(Thread.currentThread().getName()+":外卖送到了,开始干饭");
            // 买家结束(无需再次唤醒商家和卖家)
            System.out.println("下单到就餐耗时:" + (System.currentTimeMillis() - start));
        },"买家");

        // 骑手
        t3 = new Thread(()->{
            while (state < 1){// 没人点餐,等待
                System.out.println(Thread.currentThread().getName()+":没人点餐,等待中");
                LockSupport.park();// 没人点餐等待
            }

            while (state < 2){// 没有制作完成,等待
                System.out.println(Thread.currentThread().getName()+":没有制作完成,等待中");
                LockSupport.park();// 没有制作完成,等待
            }
            // 制作完成了开始送货
            System.out.println(Thread.currentThread().getName()+":接到外卖,送货中");
            try {
                // 模拟骑手送货
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+":送货完成");
            state = 3;
            LockSupport.unpark(t2);// 通知买家收货

        },"骑手");



        t1.start();
        t2.start();
        t3.start();

    }
}

可能的一个结果:

商家:没人点餐,等待中
买家:下单成功,等待送餐 <---- 1
骑手:没人点餐,等待中 <----- 2
商家:已接单,制作中
商家:制作成功
骑手:接到外卖,送货中
骑手:送货完成
买家:外卖送到了,开始干饭
下单到就餐耗时:20021

这里 1 和 2 的打印顺序出现了问题,因为 park & unpark 只是暂停&取消暂停,并没有代码同步所以会有此情况出现。

Lock 对象

java.util.concurrent.locks.Lock 是一个类似于synchronized 块的线程同步机制。但是 Lock比 synchronized 块更加灵活。Lock是个接口,它有多种实现类。其使用方式如下:

 Lock lock = ...;// Lock的实现类
 lock.lock();// 加锁
 try {
   // 访问受此锁保护的资源
 } finally {
   lock.unlock();//需要手动调用解锁(解锁操作需要在 finally 块中调用)
 }

主要方法

    /**
     * 加锁(不可打断)
     * 如果当前无法获得锁,则阻塞,直到获取到锁且不可被打断(容易死锁)
     */
    void lock();


    /**
     * 加锁(可打断)
     * 如果当前无法获得锁,则阻塞,直到获取到锁或者被打断
     *
     * @throws InterruptedException 被打断时抛出异常
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 直接尝试获得锁,如果成功返回 true,如果不成功返回 false
     * 其写法如下:
     *  Lock lock = ...;
     *  if (lock.tryLock()) {
     *    try {
     *      // 受保护的代码
     *    } finally {
     *      // 解锁
     *      lock.unlock();
     *    }
     *  } else {
     *    // 未获得锁的时候的操作,这里没有获得锁,不用解锁
     *  }
     *
     * @return 获得锁返回true,未获得锁返回false
     */
    boolean tryLock();

    /**
     * 尝试获取锁,但其会等待参数所设置的时间,在该时间内如果获得锁返回 true,如果没有获得锁返回 false
     * @param time 等待锁的最长时间
     * @param unit 时间参数的单位
     * @return 如果获取了锁,则为true;如果在获取锁之前经过了等待时间,则为false
     * @throws InterruptedException 如果当前线程在获取锁时被打断抛出此异常
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;


    /**
     * 释放锁
     * 注:如果释放的是非当前线程拥有的锁,将抛出 unchecked 异常 IllegalMonitorStateException
     */
    void unlock();

    /**
     * 创建条件对象
     */
    Condition newCondition();

ReentrantLock 可重入锁

ReentrantLock 是 Lock 接口的一个实现类

可重入

每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

简单的说就是当持有该锁的线程再次尝试获取该锁时,不会被阻塞(因为他已经持有该锁),而另外的线程尝试获取该锁时将被阻塞。

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReentrantLockTest1 {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        new Thread(()->{
            test1();
        },"t1").start();

    }


    private static void test1(){
        lock.lock();
        try{
            log.debug("进入第1层");
            test2();
        }finally {
            lock.unlock();
        }
    }

    private static void test2(){
        lock.lock();
        try{
            log.debug("进入第1层");
            test3();
        }finally {
            lock.unlock();
        }
    }

    private static void test3(){
        lock.lock();
        try{
            log.debug("进入第1层");
        }finally {
            lock.unlock();
        }
    }

}

lockInterruptibly 方法上锁(可打断)

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockInterruptiblyTest {

    private static ReentrantLock lock = new ReentrantLock();

    private static Thread t1,t2;

    public static void main(String[] args) {

       t1 = new Thread(()->{

            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.debug("被打断表示未获得锁");
            }

            try{
                log.debug("成功获得锁");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
                log.debug("释放锁");
            }

        },"t1");

       t1.start();


        t2 = new Thread(()->{

            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.debug("被打断表示未获得锁");
                // 本示例 t2 线程会被打断,进入此块
                // 这里未获取锁,要么重试,要么结束,否则会执行下面的代码,且没有加锁
                // 这样的话最后调用 unlock 方法时会抛出异常 IllegalMonitorStateException
                // 可打断的意义在于,避免 lock 方法的死等,避免死锁
            }

            try{
                log.debug("成功获得锁");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
                log.debug("释放锁");
            }

        },"t2");
        t2.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 2 秒后执行打断线程 t2 的操作
        // 由于 t1 获得锁后睡眠 5 秒,所以此处打断时,打断的是 t2 线程的 lockInterruptibly 方法,导致 t2 线程没有获得锁
        t2.interrupt();

    }

}

注:注意查看代码中的注释

tryLock方法获取锁(锁超时)

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReentrantLockTest {

    public static void main(String[] args) {

        ReentrantLock reentrantLock = new ReentrantLock();

        new Thread(()->{

            if(reentrantLock.tryLock()){
                try{
                    // 持锁线程,持有锁2秒
                    TimeUnit.SECONDS.sleep(2);
                    log.debug("try 无时间");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    reentrantLock.unlock();
                }
            }

        },"t1").start();

        try {
            // 睡眠10毫秒,以希望第一个线程先执行获得锁(这样做并不能保证第一个线程一定获得锁,此处只为验证 tryLock 的特性)
            TimeUnit.MICROSECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        new Thread(()->{

            try {
                if(reentrantLock.tryLock(5,TimeUnit.SECONDS)){
                    try{
                        // 会打印,因为当前线程会在5秒时间内尝试获取锁,第一个线程持有锁的时间为2秒
                        log.debug("try 有时间");
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"t2").start();


        new Thread(()->{

            if(reentrantLock.tryLock()){
                try{
                    // 不会打印,因为第一个线程持有所,当前线程尝试直接返回false
                    log.debug("try 第二个无时间");
                }finally {
                    reentrantLock.unlock();
                }
            }

        },"t3").start();
    }

}

执行结果:

15:37:12.409 [t1] DEBUG com.yyoo.thread.ReentrantLockTest - try 无时间
15:37:12.411 [t2] DEBUG com.yyoo.thread.ReentrantLockTest - try 有时间

t1 线程优先获得锁,所以会打印,t2 线程尝试获得锁且等待5秒,t1线程在2秒后就会释放锁,所以t2获得了锁。t3线程直接尝试获得锁,此时t1还没有释放锁,所以t3没有获得锁。

注:这里只是判断是否可以获得锁的示例所以这里使用的判断为 if 条件,请根据自身情况选择 if 还是 while

tryLock(long timeout, TimeUnit unit) 方法是可以被打断的,所以在 tryLock 等待时间内,如果被打断,依然会抛出 InterruptedException

公平锁

ReentrantLock 对象中有个 Sync 对象,其有两个实现:NonfairSync(不公平锁)、FairSync(公平锁),ReentrantLock 的两个构造函数定义如下:

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁是按照先进入阻塞队列,先获得锁的思想来实现的。可以用于解决线程饥饿的问题。但也会带来并发度降低的问题。一般都不使用。

Sync 继承自 AbstractQueuedSynchronizer 也就是大名鼎鼎的 AQS,关于 AQS 我们会在后续文章中详解,此处先了解即可,我们先了解怎么用,再来说原理

条件变量 Condition

我们还是用前面送外卖的示例来说明。synchronized 关键字实际上表示一个条件变量,Condition 是可以支持多条件变量,控制粒度更细致。

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockConditionTest {

    // 外卖订单
    private static ReentrantLock order = new ReentrantLock();

    // 买家条件
    private static Condition buyer = order.newCondition();

    // 卖家条件
    private static Condition seller = order.newCondition();

    // 骑手条件
    private static Condition rider = order.newCondition();

    /**
     * 0:未点餐
     * 1:已点餐,未制作
     * 2:制作完成,骑手未送货
     * 3:骑手送货成功,可以开始干饭了
     */
    private static int state = 0;

    public static void main(String[] args) {

        new Thread(()->{

            order.lock();

            try{
                if(state == 0) {
                    log.debug("没人点餐,等待!");
                    seller.await();
                }

                if(state == 1){
                    log.debug("开始制作");
                    TimeUnit.SECONDS.sleep(5);// 模拟制作时间
                    state = 2;
                    log.debug("制作成功");
                    rider.signal();// 通知骑手送货
                }

            } catch (InterruptedException e) {
                // await 方法可以被打断
                throw new RuntimeException(e);
            } finally {
                order.unlock();
            }

        },"卖家").start();


        new Thread(()->{

            order.lock();

            try{
                if(state == 0) {
                    log.debug("没人点餐,等待!");
                    rider.await();
                }

                if(state == 1){
                    log.debug("外卖制作中,等待!");
                    rider.await();
                }

                if(state == 2){
                    log.debug("取到外卖,开始送货");
                    TimeUnit.SECONDS.sleep(5);// 模拟送货时间
                    state = 3;
                    log.debug("外卖送到");
                    buyer.signal();// 通知买家收货
                }
            } catch (InterruptedException e) {
                // await 方法可以被打断
                throw new RuntimeException(e);
            } finally {
                order.unlock();
            }

        },"骑手").start();


        new Thread(()->{

            order.lock();

            try{
                log.debug("开始点餐");
                state = 1;
                log.debug("点餐成功");
                seller.signalAll();// 通知卖家接单制作
                log.debug("等待收外卖");
                buyer.await();// 等待收外卖
                log.debug("收到外卖,开始干饭");
            } catch (InterruptedException e) {
                // await 方法可以被打断
                throw new RuntimeException(e);
            } finally {
                order.unlock();
            }

        },"买家").start();

    }

}

执行结果

17:08:44.346 [卖家] DEBUG com.yyoo.thread.LockConditionTest - 没人点餐,等待!
17:08:44.348 [骑手] DEBUG com.yyoo.thread.LockConditionTest - 没人点餐,等待!
17:08:44.348 [买家] DEBUG com.yyoo.thread.LockConditionTest - 开始点餐
17:08:44.348 [买家] DEBUG com.yyoo.thread.LockConditionTest - 点餐成功
17:08:44.348 [买家] DEBUG com.yyoo.thread.LockConditionTest - 等待收外卖
17:08:44.348 [卖家] DEBUG com.yyoo.thread.LockConditionTest - 开始制作
17:08:49.363 [卖家] DEBUG com.yyoo.thread.LockConditionTest - 制作成功
17:08:49.363 [骑手] DEBUG com.yyoo.thread.LockConditionTest - 取到外卖,开始送货
17:08:54.368 [骑手] DEBUG com.yyoo.thread.LockConditionTest - 外卖送到
17:08:54.368 [买家] DEBUG com.yyoo.thread.LockConditionTest - 收到外卖,开始干饭

await 和 signal 以及 signalAll 方法和 wait & notify & notifyAll 方法类似,await & signal 必须要有与之关联的锁,wait & notify 只能在 synchronized 块中使用。

点外卖这个示例,其实就是实际中的线程执行的顺序性问题的解决方案之一。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合

上一篇&#xff1a; C#&#xff0c;入门教程(03)——Visual Studio 2022编写彩色Hello World与动画效果https://blog.csdn.net/beijinghorn/article/details/123478581 C#&#xff0c;入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net…

Field II 仿真软件——安装

1. 去官网下载文件压缩包 Field II Ultrasound Simulation Program (field-ii.dk) 在Download页面下载符合自己系统的压缩包。 2. 解压压缩文件&#xff0c;然后将这个文件夹添加到matlab的路径中&#xff0c;如下图所示&#xff1a; 3. 在matlab命令行输入&#xff1a;field…

05 HAL库驱动蜂鸣器唱出一首小歌

目录 一、蜂鸣器的基本知识 1、有源蜂鸣器 2、无源蜂鸣器 二、PWM的相关知识 1. PWM概念 2. PWM常见参数 3.PWM基本结构 三、蜂鸣器发出音调的原理 四、频率计算 五、实验开始 一、蜂鸣器的基本知识 蜂鸣器是一种能够发出持续而连续的声音的电子设备&#xff0c;它被…

【十一】【C++\动态规划】1218. 最长定差子序列、873. 最长的斐波那契子序列的长度、1027. 最长等差数列,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略&#xff0c;它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题&#xff0c;并将每个小问题的解保存起来。这样&#xff0c;当我们需要解决原始问题的时候&#xff0c;我们就可以直接利…

.NET进阶篇06-async异步、thread多线程2

知识须要不断积累、总结和沉淀&#xff0c;思考和写做是成长的催化剂web 内容目录 1、线程Thread 一、生命周期 二、后台线程 三、静态方法 1.线程本地存储 2.内存栅栏 四、返回值 2、线程池ThreadPool 一、工做队列 二、工做线程和IO线程 三、和Thread区别 四、定时器 1、线…

3D视觉-激光三角测量法的分类

按照入射激光光束和被测物体表面法线的角度关系&#xff0c;一般分为直射式和斜射式两种方式。 1&#xff09;直射式测量 如图所示&#xff0c;激光器发出的光线&#xff0c;经会聚透镜聚焦后垂直入射到被测物体表面上&#xff0c;物体移动或者其表面变化&#xff0c;导致入射…

纯CSS的华为充电动画,它来了

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; Krpano专栏&#xff1a;想学Krpano的&#xff0c;冲 &#x1f514…

ubuntu磁盘管理常用命令

写的不全&#xff0c;后面随时修改。 Linux 磁盘管理常用三个命令为 df、du 和 fdisk。 df&#xff08;英文全称&#xff1a;disk free&#xff09;&#xff1a;列出文件系统的整体磁盘未使用量du&#xff08;英文全称&#xff1a;disk used&#xff09;&#xff1a;检查磁盘空…

GcExcel:DsExcel 7.0 for Java Crack

GcExcel:DsExcel 7.0-高速 Java Excel 电子表格 API 库 Document Solutions for Excel&#xff08;DsExcel&#xff0c;以前称为 GcExcel&#xff09;Java 版允许您在 Java 应用程序中以编程方式创建、编辑、导入和导出 Excel 电子表格。几乎可以部署在任何地方。 创建、加载、…

[python]python利用pyaudio录制系统声音没有立体声混音怎么录制系统音频

当电脑没有立体声混音导致Python写代码无法使用pyaudio进行录制系统声音怎么办&#xff1f;查阅资料和安装驱动等方法都不行&#xff0c;难道没办法了吗&#xff1f;那为什么电脑其他软件可以做到呢&#xff1f;因此研究了一下pyaudio在没有立体声混音情况下确实无法录制声音&a…

数据模型设计

数据模型设计&#xff0c;可以理解为数据库中的表结构设计。 我们在设计器中创建的数据模型&#xff0c;也称为实体。我们将前端页面中传过来的数据保存到对应的实体中&#xff0c;即为将前端数据保存到了数据库中。 1 、实体与枚举的创建 1 .1 创建供应商 supplier实体 在左…

MySQL基础学习: 由delete和insert操作导致的死锁问题

一、问题复现&#xff1a;表结构 CREATE TABLE user_props (user_id bigint NOT NULL ,prop_key varchar(100) NOT NULL ,prop_value varchar(100) NOT NULL,PRIMARY KEY (user_id,prop_key) )二、死锁测试 &#xff08;1&#xff09;开启两个事务 &#xff08;2&#xff09;…

基于微信小程序的停车预约系统设计与实现

基于微信小程序的停车预约系统设计与实现 项目概述 本项目旨在结合微信小程序、后台Spring Boot和MySQL数据库&#xff0c;打造一套高效便捷的停车预约系统。用户通过微信小程序进行注册、登录、预约停车位等操作&#xff0c;而管理员和超级管理员则可通过后台管理系统对停车…

【python】爬取百度热搜排行榜Top50+可视化【附源码】【送数据分析书籍】

一、导入必要的模块&#xff1a; 这篇博客将介绍如何使用Python编写一个爬虫程序&#xff0c;从斗鱼直播网站上获取图片信息并保存到本地。我们将使用requests模块发送HTTP请求和接收响应&#xff0c;以及os模块处理文件和目录操作。 如果出现模块报错 进入控制台输入&#xff…

【软件工程】走进敏捷开发:灵活、协作、迭代的软件工艺之旅

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; 软件工程 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言&#xff1a; 正文 敏捷开发&#xff08;Agile Development&#xff09; 详细介绍&#xff1a; 优缺点&#xff1a; 优点&#xf…

(2023)PanGu-Draw:通过时间解耦训练和可重用的 Coop-Diffusion 推进资源高效的文本到图像合成

PanGu-Draw: Advancing Resource-Efficient Text-to-Image Synthesis with Time-Decoupled Training and Reusable Coop-Diffusion 公众&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要…

php的laravel权限问题

1.这是我新建的一个路由&#xff0c;然后就是说每新建一个路由都要给他开个权限&#xff01;&#xff01;&#xff01;&#xff01; 2.这个是组内大佬写的&#xff1a; 我们也可以在里面加&#xff0c;也可以在浏览器的页面手动加&#xff08;对我们新手来说还是浏览器的页面…

使用uni-app editor富文本组件设置富文本内容及解决@Ready先于onload执行,无法获取后端接口数据的问题

开始使用富文本组件editor时&#xff0c;不知如何调用相关API设置富文本内容和获取内容&#xff0c;本文将举例详解 目录 一.了解editor组件的常用属性及相关API 1.属性常用说明 2.富文本相关API说明 1&#xff09;editorContext 2&#xff09; editorContext.setContents…

JVM 常用知识和面试题

1. 什么是JVM内存结构&#xff1f; jvm将虚拟机分为5大区域&#xff0c;程序计数器、虚拟机栈、本地方法栈、java堆、方法区&#xff1b; 程序计数器&#xff1a;线程私有的&#xff0c;是一块很小的内存空间&#xff0c;作为当前线程的行号指示器&#xff0c;用于记录当前虚拟…

Avalonia学习(十五)-OxyPlot

今天开始继续Avalonia练习。展示一些样例&#xff0c;尤其是第三方库的使用。 本节&#xff1a;OxyPlot 1.引入OxyPlot.Avalonia 2.项目引入 在Main方法里增加OxyPlotModule.EnsureLoaded()方法调用。 public static void Main(string[] args) {OxyPlotModule.EnsureLoade…