【Java学习】多线程JUC万字超详解

news2024/9/21 22:39:43

 所属专栏:Java学习      

在这里插入图片描述

1. 多线程的概念

线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程的实际运作单位

下面这些每一个能够运行的软件就是一个进程

并发:在同一时刻,有多个指令在单个CPU上交替执行

并行:在同一时刻,有多个指令在多个CPU上同时执行

2. 多线程的实现方式

2.1 继承Thread类的方式进行实现

实现方式:

1. 定义一个类,继承Thread

2. 重写run方法

3. 创建子类对象,并启动线程

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "hello");
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        //设置线程对象名称
        t1.setName("线程一:");
        t2.setName("线程二:");
        //开启线程
        t1.start();
        t2.start();
    }
}
 

在开启线程之后,会交替执行线程一和线程二

2.2 实现Runnable接口的方式进行实现

实现方式:

  1. 自定义一个类,实现Runnable接口

  2. 重写里面的Run方法

  3. 创建自定义类的对象

  4. 创建一个Thread类的对象,并开启线程

public class MyRun implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i < 10;i++){
            //获取当前线程对象
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + "hello");
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        //创建MyRun对象,表示多线程要执行的任务
        MyRun myRun = new MyRun();
        //创建线程的对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);
        //给线程设置名字
        t1.setName("线程一:");
        t2.setName("线程二:");
        //开启线程
        t1.start();
        t2.start();
    }
}

开启线程之后也是交替执行线程一和线程二

2.3 利用Callable接口和Future接口方式实现

实现方式:

1. 创建一个类MyCallable实现Callable接口

2. 重写call方法(返回值代表多线程运行的结果)

3. 创建MyCallable对象(表示多线程要执行的任务)

4. 创建FutureTask对象(作用管理多线程运行的结果)

5. 创建Thread类的对象并启动(表示线程)

import java.util.concurrent.Callable;
​
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}
public class ThreadDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable对象(表示多线程要执行的任务)
        MyCallable myCallable = new MyCallable();
        //创建FutureTask对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(myCallable);
        //创建Thread类的对象并启动(表示线程)
        Thread t1 = new Thread(ft);
        t1.start();
        Integer res = ft.get();
        System.out.println(res);
    }
}

2.4 三种实现方式对比

优点缺点
继承Thread类编程简单,可以直接使用Thread中的方法可拓展性差,不能再继承其他类
实现Runnable接口拓展性强,实现该接口同时还可以继承其他类编程相对复杂,不能直接使用Thread中的方法
实现Callable接口

3. 常见的成员方法

3.1 getName()和setName()

对于setName()来说,如果没有给线程设置名称,也是有默认的名字的,格式:Thread - X(x序号从0开始)

根据Thread类的空参构造可以看出,在创建对象时的默认名称格式

那怎么在自定义类中创建对象时就传入对象名称进行构造呢?

在多线程第一种实现方式中,自定义的类继承了Thread类,但是构造方法并没有继承,所以还需要在自定义类中手动的去实现构造方法

3.2 currentThread()和sleep()

currentThread可以获取当前线程的对象

当JVM虚拟机启动之后,会自动开启多条线程,其中一条线程就叫做main线程,作用就是调用main方法,并执行里面的代码

sleep()是让线程休眠指定的时间,单位是毫秒,哪条线程执行到这个方法,那么哪条线程就会休眠,时间到了之后会继续执行下面的操作

这里的异常可以直接抛出

 

再来看MyThread类,这里的异常处理不能使用Throw抛出了,因为父类Thread没有抛出,这里只能使用try-catch处理异常  

3.3 getPriority()和setPriority()

3.3.1 CUP的调度方式

CUP的调度方式是有两种的:分为抢占性调度和非抢占性调度

抢占式调度是一种允许高优先级线程中断低优先级线程的执行,从而立即获得CPU资源的调度方式。在这种模式下,操作系统会定期检查线程的优先级,并根据需要切换线程的执行, 对线程的访问是随机的

非抢占式调度是一种允许线程独占CPU直到其主动放弃或执行完毕的调度方式。在这种模式下,线程的执行时间由线程本身控制,调度器不会中断正在执行的线程,轮流执行线程

3.3.2 优先级

来看Thread类中优先级的设置,最小为1,最大为10,默认是5,优先级越高抢占到CUP的概率越高,只是说概率高,并不是优先级高的就肯定比优先级低的要先抢占到CUP

 

public class ThreadTest2 {
    public static void main(String[] args) {
        //创建线程要执行参数的对象
        MyRunable myRunable = new MyRunable();
        //创建线程对象
        Thread thread1 = new Thread(myRunable,"线程一");
        Thread thread2 = new Thread(myRunable,"线程二");

        //获取线程优先级
        System.out.println(thread1.getPriority());
        thread2.setPriority(10);
        System.out.println(thread2.getPriority());

        System.out.println(Thread.currentThread().getPriority());
    }
}

3.4 setDaemon()

设置为守护线程也类似于备胎线程,当其他线程执行完毕之后,守护线程会陆续结束(并不是立即结束)

public class ThreadTest3 {
    public static void main(String[] args) {
        MyThread1 thread1 = new MyThread1();
        MyThread2 thread2 = new MyThread2();
        thread1.setName("女神");
        thread2.setName("备胎");
        //把第二个线程设置为守护线程
        thread2.setDaemon(true);
        thread1.start();
        thread2.start();
    }
}

应用场景:例如在聊天软件中,当打开聊天窗口之后,开启聊天窗口的线程和发送文件的线程,这时就可以把发送文件设置为守护线程,当聊天窗口关闭之后,守护线程也就没有存在的必要了  

4. 多线程的生命周期

多线程的生命周期是指一个线程从创建到消亡的整个过程。在这个过程中,线程会经历不同的状态。一般来说,线程的生命周期可以归纳为以下几个主要阶段:

1. 新建(New)

  • 状态描述:线程被创建但尚未启动。使用new关键字和Thread类或其子类创建一个线程对象后,该线程就处于新建状态。此时,线程仅在内存中被分配了空间,但还没有开始执行。

  • 注意:在操作系统层面,真正的线程还没有被创建,只有调用了start()方法后,线程才会被操作系统创建并进入下一个状态。

2. 就绪(Runnable)

  • 状态描述:线程已经启动,但尚未获得CPU执行权,处于等待CPU分配资源的阶段。调用线程的start()方法后,线程会进入就绪状态。此时,线程已经具备了运行条件,但还需要等待系统为其分配CPU资源。

  • 注意:就绪状态并不是执行状态,线程需要等待CPU的调度才能进入运行状态。

3. 运行(Running)

  • 状态描述:线程获得CPU资源并执行其run()方法中的代码。当就绪状态的线程被系统选中并获得CPU执行权时,它会进入运行状态。

  • 状态转变:运行状态的线程可以转变为阻塞状态、就绪状态和死亡状态。如果线程失去了CPU资源,它会从运行状态转变为就绪状态;如果线程执行了某些导致阻塞的操作(如调用sleep()wait()方法或进行I/O操作),它会进入阻塞状态;如果线程的run()方法执行完毕或被强制终止,它会进入死亡状态。

4. 阻塞(Blocked)

  • 状态描述:线程因为某些原因(如等待I/O操作完成、等待获取同步锁等)而暂停执行。处于阻塞状态的线程不会占用CPU资源,直到阻塞的原因被消除后,线程才会重新进入就绪状态并等待CPU的调度。

  • 状态转变:阻塞状态的线程可以转变为就绪状态和死亡状态。当阻塞的原因被消除(如I/O操作完成、获取到同步锁等),线程会重新进入就绪状态;如果线程在等待过程中被强制终止(如调用stop()方法),它会进入死亡状态。

5. 死亡(Terminated/Dead)

  • 状态描述:线程的生命周期结束。当线程的run()方法执行完毕或被强制终止时,线程会进入死亡状态。此时,线程所占用的资源会被释放。

  • 注意:死亡的线程不能被再次启动。如果尝试在死亡的线程上调用start()方法,会抛出IllegalThreadStateException异常。

5. 线程安全问题

先来看一个现象: 

使用多线程实现三个窗口卖票的业务

这时就出现了一些小问题,售卖的票中有相同的票,也有超出范围的票,出现这个问题的原因就是线程执行时是有随机性的,当一个线程休眠时,其他的线程就可以抢到CPU了,休眠之后就又可以争夺CPU,此时如果一个线程刚好执行到target++,还没来得及打印,其他线程抢回了CPU,并且执行了target++,这时就可能出现以上的情况

解决办法:把操作共享数据的代码锁起来,锁默认打开,如果有现成进去之后,锁自动关闭,里面的代码全部执行完毕,线程出来,锁自动打开,这样就可以解决上述问题

5.1 同步代码块

同步代码块是通过关键字synchronized来实现的,括号中需要传入一个锁对象,可以是任意的,但必须是唯一的,通常会使用Thread.class作为锁对象,因为字节码文件对象是唯一的

synchronized (锁对象){
            
}
public class MyThread3 extends Thread {
    static int ticket = 0;
    @Override
    public void run() {
        while (true) {
            synchronized (Thread.class) {
                if (ticket < 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println("正在卖第" + ticket + "张票");
                } else {
                    break;
                }
            }

        }
    }
}

要注意的是,synchronized在这里不能写在while循环外面,不然的话只有线程一就把循环的内容执行完了,然后剩余的线程由于target不满足循环条件,就不会再执行了  

5.2 同步方法

把synchronized加在方法上就是同步方法

格式:修饰符 synchronized 返回类型方法名(方法参数){...};

特点:同步方法是锁住方法里面所有的代码,锁对象不能自己指定,在非静态方法中,锁对象为this所指的对象,在static静态方法中,锁对象指的是当前类的字节码文件的对象

还是上面的例子,这次 实现 Runnable接口,使用同步方法试一下

public class MyRunnable implements Runnable {
    int ticket = 0;

    @Override
    public void run() {
        while (true) {
            if (func()) break;
        }
    }

    private synchronized boolean func() {
        if (ticket == 100) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
        }
        return false;
    }
}

6. 锁

上面的同步代码块和同步方法虽然也是起到了把一段代码锁起来的效果,但是并没有直接看出哪里加上了锁,哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中也提供了获得锁和释放锁的方法

void lock() : 获得锁

void unlock() : 释放锁

Lock是一个接口,所以需要通过它的实现类ReentrantLock来实例化对象,然后再调用上面两个方法

以之前创建的MyThread3为例,由于需要创建三个MyThread3的对象,所以在MyThread3中创建的锁对象也会被创建三次,那么就会出现之前超出范围的问题,所以创建的锁对象要用static修饰一下

但这时会出现一个问题,程序最终并没有停止

 这是因为假如线程一抢到了CPU,并执行完毕之后跳出了循环,线程二和线程三还在锁的外面,所以需要改变释放锁的位置,可以利用 finally 来解决这个问题

public class MyThread3 extends Thread {
    static int ticket = 0;
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //synchronized (Thread.class) {
            lock.lock();
            try {
                if (ticket == 100) {
                    break;
                } else {
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            // }
        }
    }
}

6.1 死锁

它指的是两个或多个线程在执行过程中,由于竞争资源而造成的一种阻塞现象,若无外力作用,这些线程都将无法向前推进,死锁通常发生在两个或多个线程相互等待对方释放锁的情况,一般就是在锁的嵌套中容易发生,所以要避免这种写法

7. 等待唤醒机制

void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

等待(wait):当一个线程执行到某个对象的wait()方法时,它会释放当前持有的锁(如果有的话),并进入等待状态。此时,线程不再参与CPU的调度,直到其他线程调用同一对象的notify()或notifyAll()方法将其唤醒。

唤醒(notify/notifyAll):

notify: 唤醒在该对象监视器上等待的某个线程,如果有多个线程在等待,那么具体唤醒哪一个是随机的

notifyAll: 唤醒在该对象监视器上等待的所有线程

调用wait方法的线程会释放其持有的锁,被唤醒的线程在执行之前,必须重新获取被释放的锁

public class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 0) {
                        try {
                            Desk.lock.wait();//厨师等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        Desk.count--;
                        System.out.println("还能再吃" + Desk.count + "碗");
                        Desk.lock.notifyAll();//唤醒所有线程
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 1) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("已经做好了");
                        Desk.foodFlag = 1;
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Desk {
    public static int foodFlag = 0;

    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}

这里实现的功能就是,厨师做好事务放在桌子上,美食家开始品尝,如果桌子上没有食物,美食家就等待,有的话,厨师进行等待

7.1 阻塞队列

在Java中,阻塞队列(BlockingQueue)是java.util.concurrent包下的一个接口,它支持两个附加操作的队列。这两个附加的操作是:在元素从队列中取出时,如果队列为空,则等待直到队列中有元素可取;在元素添加到队列时,如果队列已满,则等待直到队列中有空间可用。阻塞队列主要用于生产者-消费者场景,其中生产者线程用于向队列中添加元素,而消费者线程从队列中取出元素。

  • 生产者-消费者场景:在多线程环境下,生产者线程向队列中添加元素,消费者线程从队列中取出元素。阻塞队列能够平衡生产者和消费者的处理能力。

  • 任务队列:在异步处理框架中,将待处理的任务放入阻塞队列中,由线程池中的线程去取任务并执行。

public class Cook1 extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            try {
                queue.put("美食");
                System.out.println("已经做好了一道美食");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Foodie1 extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            try {
                String food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadTest6 {
    public static void main(String[] args) {
        //必须指定阻塞队列的上限
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        Cook1 cook1 = new Cook1(queue);
        Foodie1 foodie1 = new Foodie1(queue);
        cook1.start();
        foodie1.start();
    }
}

在Cook1和Foodie1两个类中,发现并没有去使用同步代码块或是锁,因为take()和put()这两个方法在底层已经使用到了锁,如果再使用锁就会发生死锁

此外,由于锁是在方法内部使用的,所以方法外面的打印语句由于CPU抢占的原因,可能发生之前重复打印的情况

8. 多线程的六种状态

多线程的六种状态分别为:新创建,可运行,被阻塞,等待,计时等待,被终止

在Java中是没有定义运行状态的,因为当线程抢夺到CPU的执行权之后,接下来就该交给操作系统了,也没有必要再定义了

9. 线程池

9.1 线程池的概念和使用

在之前我们写的代码中,用到线程就创建,用完之后线程就消失了,这样会浪费操作系统的资源,也存在一些弊端,通过线程池就可以解决这个问题

线程池是一种线程使用模式,它维护着多个线程,等待着监督管理者分配可并发执行的任务

线程池的核心原理:

  1. 创建一个空的线程池

  2. 提交任务时,线程会创建新的线程对象,任务分配完毕,线程归还给线程池,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可

  3. 如果提交任务时,线程池中没有空闲线程,也无法创建新的线程,任务就会排队等待

public static ExecutorService newCachedThreadPoll()创建一个没有上限的线程池
public static ExecutorService newCachedThreadPoll(int nThread)创建有上限的线程池

先来正常的获取线程池对象,提交任务,销毁线程

public class ThreadTest7 {
    public static void main(String[] args) throws InterruptedException {
        //获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //提交任务
        pool1.submit(new MyRunable());
        //销毁线程池
        pool1.shutdown();
    }
}

线程复用:

线程池中的线程是复用的,一旦一个线程完成了它的任务,他就会回到线程池中等待下一个任务,如果任务提交的速度不快,或者线程池的配置较小,那么就可能看到同一个线程被用来执行多个任务

例如上面的代码中,每次提交任务,如果线程池中有空闲的线程,就会复用,而不是创建新的线程

9.2 自定义线程池

通过上面介绍的静态方法创建出来的线程池不够灵活,如果说等待的线程过多,阻塞队列中已经排满了线程,这时修改起来就不好操作,使用自定义线程池可以对所有现成进行同一管理和监控,便于及时发现问题,并及时进行配置和调整

创建自定义线程池用到的参数:核心线程的数量,线程池中最大线程池的数量,空闲时间(值),空闲时间(单位),阻塞队列,创建线程的方式,要执行的任务过多时的解决方案

解释:核心线程就是指一直存在于线程池中的线程,两个空闲时间就是值,创建出来的临时线程空闲的时间,超过这个时间就意味着这靠核心线程就足以完成当前提交的任务,就需要销毁临时线程,节约资源,要执行的任务过多时的解决方案指的是,当前线程池中线程的数量已经达到了最大,并且阻塞队列也已经排满了,就需要把多出来的任务踢出去

不断地提交任务,会有以下三个临界点:

1. 当核心线程满了之后,再提交任务就会排队

2. 当核心线程和阻塞队列都满了之后,就会创建临时线程

3. 当核心线程,阻塞队列,临时线程都满了之后,会触发任务的拒绝策略

任务拒绝策略:默认使用丢弃任务并抛出RejectedExecutionException异常

public class MyThreadPoolDemo1 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,//核心线程数量,不能小于0
                6,//最大线程数,不能小于0,大于等于核心线程数
                60,//最大存活时间
                TimeUnit.SECONDS,//单位,用TimeUnit指定
                new ArrayBlockingQueue<>(3),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//AbortPolicy是ThreadPoolExecutor的静态内部类
        );
    }
}

最大并行数是指计算机系统或软件在处理任务时能够同时执行的最大指令或数据数量。

线程池多大合适:

CPU密集型运算:计算比较多,采用最大线程数 + 1

I/O密集型运算:如果读取本地文件或读取数据库的操作比较多

在这里插入图片描述

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

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

相关文章

网络编程学习:TCP/IP协议

TCP/IP协议简介 TCP/IP协议包含了一系列的协议&#xff0c;也叫TCP/IP协议族&#xff08;TCP/IP Protocol Suite&#xff0c;或TCP/IP Protocols&#xff09;&#xff0c;简称TCP/IP。 分层结构 为了能够实现不同类型的计算机和不同类型的操作系统之间进行通信&#xff0c;引…

Java中的锁(四)利用读写锁实现高性能网页缓存

文章目录 背景Ehcache2源码解析-如何实现缓存网页读写锁ReentrantReadWriteLock解析读写锁的特性读写锁是如何实现的&#xff1f; 如何将Ehcach2-web的源码迁移到Ehcach3中&#xff1f;/ 如何自定义Filter实现高性能网页缓存&#xff1f; 背景 在我们的销售页面&#xff0c;有…

Django+Vue酒店推荐系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作者&…

Linux文件操作(二)

Linux文件操作&#xff08;二&#xff09; 导语文件和目录维护chmodchownlink及其变体dir及其变体chdir和getcwd 扫描目录opendirreaddirtelldirseekdirclosedir示例程序 错误处理strerrorperror /procfcntlmmap相关mmapmsyncmunmap 总结参考文献 导语 文件操作的第二部分&…

juzige/Monitoring-System-基于Java语言的光伏监控系统

Photovoltaic-Monitoring-System-Based-on-Java-Language 基于Java语言的光伏监控系统光伏发电系统光伏软件系统光伏监控系统源码光伏发电系统源码-智电云 一、 介绍 光伏光伏发电预测逆变器监控逆变器数据采集光伏运维光伏电站光伏功率预测光伏监控系统光伏发电系统光伏软件…

【CSS in Depth 2 精译_022】3.6 一招搞定容器内元素间距的问题 + 3.7 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

jQuery入门(六)jQuery实现瀑布流分页案例

一、瀑布流分页案例分析 1.1) 功能分析&#xff1a; 鼠标下拉&#xff0c;加载分页数据(10条) &#xff0c;如下图&#xff1a; 案例分析&#xff1a; 1.2) 如何确定当前显示的数据已经浏览完毕&#xff1f; 公式&#xff1a;(滚动条距底部的距离 滚动条上下滚动的距离 当…

【Python篇】Python 类和对象:详细讲解(下篇)

文章目录 Python 类和对象&#xff1a;详细讲解&#xff08;下篇&#xff09;15. 接口和协议&#xff08;Interfaces and Protocols&#xff09;15.1 什么是接口&#xff1f;15.2 协议的基本概念例子&#xff1a;定义飞行协议详细解释输出示例 16. 装饰器模式&#xff08;Decor…

A02、Java编程性能调优(02)

1、Stream如何提高遍历集合效率 1.1、什么是Stream 现在很多大数据量系统中都存在分表分库的情况。例如&#xff0c;电商系统中的订单表&#xff0c;常常使用用户 ID 的 Hash 值来实现分表分库&#xff0c;这样是为了减少单个表的数据量&#xff0c;优化用户查询订单的速度。 …

ZYNQ-Utlscale-RFSOC看门狗

ZYNQ-Utlscale-RFSOC看门狗复位 ZYNQ-Utlscale-RFSOC 看门狗的程序网上里程很少&#xff0c;开源资料也是几乎没有&#xff0c;最近需要用到这个功能&#xff0c;来来回回搞了一周才搞定。刚开始参考ZYNQ7000的资源&#xff0c;发现MPSOC不适用。很感谢下面的几篇文章&#xf…

探索TinyDB:轻量级数据库的优雅之旅

文章目录 探索TinyDB&#xff1a;轻量级数据库的优雅之旅背景&#xff1a;为何选择TinyDB&#xff1f;TinyDB是什么&#xff1f;如何安装TinyDB&#xff1f;简单库函数使用方法场景应用常见Bug及解决方案总结 探索TinyDB&#xff1a;轻量级数据库的优雅之旅 背景&#xff1a;为…

【电子通识】电子元器件可靠性基本概念

什么是电子元器件 电子元器件是电子产品的基本组成单元&#xff0c; 是电子元件和电子器件的总称。 通常电子元件指的是无源元件&#xff0c; 电子器件指的是有源器件。无源元件是对所供给的电能执行被动操作&#xff08;如耗散、储蓄或释放等&#xff09;的元件&#xff0c; 如…

《普通人学AI指南PDF》免费下载破10万。。。

你好&#xff0c;我是郭震 普通人如何开始AI&#xff0c;用好AI&#xff1f; 我在今年制作的《普通人学AI指南》PDF&#xff0c;从中或许能帮你找到答案&#xff1a; 1 PDF背景介绍 此PDF我是今年开始着手制作&#xff0c;到6月8日终版&#xff0c;中间历经反复多次修改&#x…

LabVIEW布尔值比较与信号状态上升沿下降沿检测

在 LabVIEW 编程中&#xff0c;布尔值的比较不仅是逻辑运算的重要组成部分&#xff0c;还广泛应用于信号的上升沿和下降沿检测。通过理解 True > False 这样的基本表达式&#xff0c;以及如何在程序中检测信号的状态变化&#xff0c;开发者可以有效地控制系统行为&#xff0…

内存管理篇-17解开页表的神秘面纱-上

--好像并没有解开。。 1.遗留的一些问题 &#xff08;1&#xff09;页表存储在内存的什么地方&#xff1f;页表服用的对象就是MMU&#xff0c;主要就为了MMU单元翻译的时候提供作用&#xff0c;因此能存放在MMU中的TLB缓存中&#xff0c;也可能存放在内存中。但是在内存中的什…

【MATLAB】matlab生成的图像如何导出(三种方法教会你)

我们经常使用matlab生成各类的图&#xff0c;如何将其导出&#xff0c;导出为何种类型。 方法一&#xff1a;选择 matlab 生成的图形界面 " Figure 1 " 的菜单栏 " 编辑 " — " 复制图窗 " , 就可以将图像拷贝到 Word 文档中 打开 Word 文档 ,…

远程调用以及注册中心Nacos

小编目前大一&#xff0c;刚开始着手学习微服务的相关知识&#xff0c;小编会把它们整理成知识点发布出来。我认为同为初学者&#xff0c;我把我对知识点的理解以这种代码加观点的方式分享出来不仅加深了我的理解&#xff0c;或许在某个时候对你也有所帮助&#xff0c;同时也欢…

YOLOv5课堂行为识别系统+GUI界面

课堂行为检测 gui/课堂行为识别系统/YOLOv5课堂行为识别/ yolov5/opencv/计算机视觉/python程序/深度学习/pytorch 数据集标注/配置好环境程序可直接运行/带UI界面/代码数据集/代码数据集 &#xff3b;功能&#xff3d;图片识别/视频识别/摄像头识别 损失/准确率等数据可在ten…

Golang 教程3——包

Golang 教程3——包 注意&#xff0c;该文档只适合有编程基础的同学&#xff0c;这里的go教程只给出有区别的知识点 文件结构 前置工作 在文件testproject01目录下执行 ‘go mod init gocode/testproject01’ (base) PS E:\Goproject\src\gocode\testproject01> go mo…

Linux学习——Ubuntu上QT添加资源

在我们平时的车载控制屏幕上一般不会只有文字和黑白的按钮&#xff0c;为了美观和容易操作&#xff0c;在设计的时候一般会添加图片或者是图标&#xff0c;来让界面的人机交互达到最好&#xff0c;那么我们今天就来学习一下如何在QT中添加资源图片&#xff01; 1.传输照片&…