Java多线程深入探讨

news2024/11/24 3:46:22

    • 1. 线程与进程
    • 2. 创建和管理线程
      • 2.1. 继承Thread类
      • 2.2. 实现Runnable接口
      • 2.3 利用Callable、FutureTask接口实现。
      • 2.4 Thread的常用方法
    • 3. 线程同步
      • 3.1. synchronized关键字
        • 3.1.1同步代码块:
        • 3.1.2 同步方法:
      • 3.2. Lock接口
    • 4. 线程间通信
    • 5. 线程池
      • 5.1 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
      • 5.2 使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
    • 6. Java并发工具类
      • 6.1. CountDownLatch
      • 6.2. CyclicBarrier
      • 6.3. Semaphore
      • 6.4. ConcurrentHashMap
      • 6.5. BlockingQueue
    • 7. 总结
    • 9. 线程优先级
    • 10. 守护线程
    • 11. 线程局部变量
    • 12. 线程异常处理
    • 13. 实践建议

Java多线程编程是Java程序员必备的技能之一,因为它可以帮助我们编写更高效、更快速的程序。在本文中,我们将深入探讨Java多线程的基本概念、线程的创建和管理、同步、线程池以及Java并发工具类。

1. 线程与进程

进程是操作系统进行资源分配的基本单位,它是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是系统进行资源分配和调度的一个独立单位。

线程是进程中的一个实体,它是被操作系统独立调度和执行的基本单位。一个进程中可以有多个线程,这些线程共享进程的资源,如内存、文件句柄等。线程之间的切换比进程切换要快得多,因为线程间通信成本更低。

2. 创建和管理线程

在Java中,有两种方法可以创建线程:(还有JDK5新增了一种方法:实现Callable接口)

2.1. 继承Thread类

步骤:

①定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

②创建MyThread类的对象

③调用线程对象的start()方法启动线程(启动后还是执行run方法的)

创建一个新类,该类继承自Thread类,然后覆盖run方法:

class MyThread extends Thread {
    @Override
    public void run() {
        // 你的线程代码
    }
}

// 在主程序中创建并启动线程:
MyThread myThread = new MyThread();
myThread.start();

优缺点:

  • 优点:编码简单
  • 缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展

注意:

1、为什么不直接调用了run方法,而是调用start启动线程。

  • ​ 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
  • ​ 只有调用start方法才是启动一个新的线程执行。

2、把主线程任务放在子线程之前了。

  • 这样主线程一直是先跑完的,相当于是一个单线程的效果了。

2.2. 实现Runnable接口

步骤:

①定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

②创建MyRunnable任务对象

③把MyRunnable任务对象交给Thread处理。

④调用线程对象的start()方法启动线程

创建一个新类,实现Runnable接口,然后实现run方法:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 你的线程代码
    }
}

// 在主程序中创建并启动线程:
Thread thread = new Thread(new MyRunnable());
thread.start();

实现Runnable接口通常是更好的选择,因为它允许你的类继承其他类,而Java不支持多继承。

优缺点:

  • 优点:线程任务类只是实现了Runnale接口,可以继续继承类和实现接口,扩展性强。
  • 缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

Thread的构造器

构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)封装Runnable对象成为线程对象
public Thread(Runnable target ,String name )封装Runnable对象成为线程对象,并指定线程名称

2.3 利用Callable、FutureTask接口实现。

前言:

1、前2种线程创建方式都存在一个问题:

  • 他们重写的run方法均不能直接返回结果。
  • 不适合需要返回线程执行结果的业务场景。

2、怎么解决这个问题呢?

  • JDK 5.0提供了Callable和FutureTask来实现。
  • 这种方式的优点是:可以得到线程执行的结果。

步骤:

①、得到任务对象

​ 1.定义类实现Callable接口,重写call方法,封装要做的事情。

​ 2.用FutureTask把Callable对象封装成线程任务对象。

②、把线程任务对象交给Thread处理。

③、调用Thread的start方法启动线程,执行任务

④、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。

FutureTask的API

方法名称说明
public FutureTask<>(Callable call)把Callable对象封装成FutureTask对象。
public V get() throws Exception获取线程执行call方法返回的结果。

优缺点:

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 可以在线程执行完毕后去获取线程执行的结果。
  • 缺点:编码复杂一点。

总结:

方式优点缺点
继承Thread类编程比较简单,可以直接使用Thread类中的方法扩展性较差,不能再继承其他的类,不能返回线程执行的结果
实现Runnable接口扩展性强,实现该接口的同时还可以继承其他的类。编程相对复杂,不能返回线程执行的结果
实现Callable接口扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果编程相对复杂

2.4 Thread的常用方法

Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。

  1. 当有很多线程在执行的时候,我们怎么去区分这些线程呢?

此时需要使用Thread的常用方法:getName()、setName()、currentThread()等。

Thread获取和设置线程名称

方法名称说明
String getName()获取当前线程的名称,默认线程名称是Thread-索引
void setName(String name)将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称

Thread类获得当前线程的对象

方法名称说明
public static Thread currentThread():返回对当前正在执行的线程对象的引用

注意:

1、此方法是Thread类的静态方法,可以直接使用Thread类调用。 2、这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

Thread类的线程休眠方法

方法名称说明
public static void sleep(long time)让当前线程休眠指定的时间后再继续执行,单位为毫秒。

3. 线程同步

在多线程环境下,如果多个线程同时访问共享资源,可能会导致数据不一致的问题。为了解决这个问题,我们需要对共享资源进行同步。

线程同步的核心思想

  • 加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

3.1. synchronized关键字

在Java中,我们可以使用synchronized关键字来实现同步。synchronized可以修饰方法或者代码块。当一个线程进入synchronized修饰的方法或代码块时,其他线程将被阻塞,直到该线程离开。

以下是一个使用synchronized关键字的示例:

class Counter {
    private int count;

    public synchronized void increment() {
        count++;
    }

    public synchronized void decrement() {
        count--;
    }

    public synchronized int getCount() {
        return count;
    }
}

3.1.1同步代码块:

作用:把出现线程安全问题的核心代码给上锁。

原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
在这里插入图片描述

锁对象要求

  • 理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可。

锁对象的规范要求

  • 规范上:建议使用共享资源作为锁对象。

  • 对于实例方法建议使用this作为锁对象。

  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

3.1.2 同步方法:

作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qAe9eag-1682872681762)(C:\Users\28333\AppData\Roaming\Typora\typora-user-images\image-20230501002606877.png)]

原理:

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

3.2. Lock接口

除了使用synchronized关键字外,Java还提供了java.util.concurrent.locks包中的Lock接口,该接口允许我们实现更灵活、更细粒度的同步。ReentrantLockLock接口的一个实现,它允许线程以可重入的方式获取锁。

以下是一个使用ReentrantLock的示例:

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

class Counter {
    private int count;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

4. 线程间通信

线程间通信是指多个线程之间互相发送消息或数据的过程。在Java中,我们可以使用waitnotifynotifyAll方法实现线程间通信。

线程通信常见形式

  • 通过共享一个数据的方式实现。
  • 根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

应用场景:

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。

  • 要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。

方法名称说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

以下是一个使用waitnotify实现生产者消费者模型的示例:

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        int maxSize = 10;

        Thread producer = new Thread(new Producer(queue, maxSize), "Producer");
        Thread consumer = new Thread(new Consumer(queue), "Consumer");

        producer.start();
        consumer.start();
    }
}

class Producer implements Runnable {
    private Queue<Integer> queue;
    private int maxSize;

    public Producer(Queue<Integer> queue, int maxSize) {
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        int value = 0;
        while (true) {
            synchronized (queue) {
                while (queue.size() == maxSize) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("Producing " + value);
                queue.add(value++);
                queue.notifyAll();
            }
        }
    }
}

class Consumer implements Runnable {
    private Queue<Integer> queue;

    public Consumer(Queue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                int value = queue.poll();
                System.out.println("Consuming " + value);
                queue.notifyAll();
            }
        }
    }
}

5. 线程池

线程池是一种管理线程的机制,它允许我们重用线程以减少创建和销毁线程的开销。在Java中,我们可以使用ExecutorService接口和Executors类来创建和管理线程池。

5.1 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

ExecutorService
ThreadPoolExecutor

ThreadPoolExecutor构造器的参数说明
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99K71aWk-1682872681763)(C:\Users\28333\AppData\Roaming\Typora\typora-user-images\image-20230501003214497.png)]

注:

临时线程什么时候创建啊?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

ExecutorService的常用方法

方法名称说明
void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task)执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown()等任务执行完毕后关闭线程池
List<Runnable]> shutdownNow()立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

5.2 使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

Executors得到线程池对象的常用方法

  • Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称说明
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。
public static ExecutorService newFixedThreadPool(int nThreads)创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor ()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

以下是一个使用线程池的示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executorService.execute(worker);
        }

        executorService.shutdown();

        while (!executorService.isTerminated()) {
        }

        System.out.println("All threads finished");
    }
}

class WorkerThread implements Runnable {
    private String message;

    public WorkerThread(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " processing message: " + message);
        processMessage();
    }

    private void processMessage() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6. Java并发工具类

Java并发包java.util.concurrent提供了许多并发工具类,它们可以帮助我们简化多线程编程。以下是一些常用的并发工具类:

6.1. CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。它包含一个初始计数值,任何调用await方法的线程都将被阻塞,直到计数值为0。

6.2. CyclicBarrier

CyclicBarrier允许一组线程相互等待,直到所有线程都到达某个屏障点。当最后一个线程到达屏障点时,所有等待的线程将被释放。

6.3. Semaphore

Semaphore用于限制可以访问某个资源的线程数量。当一个线程要访问受限资源时,需要先获得信号量;当线程完成操作时,释放信号量。如果没有可用的信号量,线程将阻塞,直到有线程释放信号量。

6.4. ConcurrentHashMap

ConcurrentHashMap是一个线程安全的HashMap实现,它允许多个线程并发访问。与使用synchronized关键字的HashMap相比,它具有更好的性能。

6.5. BlockingQueue

BlockingQueue是一个阻塞队列,它支持线程安全的插入和删除操作。当队列为空时,删除操作将阻塞;当队列满时,插入操作将阻塞。BlockingQueue在生产者-消费者模型中非常有用。

7. 总结

本文深入探讨了Java多线程编程的基本概念、线程的创建和管理、同步、线程池以及Java并发工具类。了解这些概念和技术对于编写高效、高性能的Java程序至关重要。希望本文能为你的Java多线程编程之旅提供有用的指导!## 8. 线程的状态和生命周期

在Java中,线程具有以下状态:

  1. 新建(New):当我们创建了一个线程对象,但还没有调用start()方法时,线程处于新建状态。
  2. 就绪(Runnable):当线程对象调用了start()方法后,线程进入就绪状态。在这个状态下,线程已经准备好运行,等待操作系统分配时间片。
  3. 运行(Running):线程获得时间片并开始执行run()方法中的代码时,线程处于运行状态。
  4. 阻塞(Blocked):线程在等待获取锁以进入同步代码块或方法时,线程进入阻塞状态。
  5. 等待(Waiting):线程在等待其他线程执行特定操作(如调用notify()notifyAll()方法)时,线程进入等待状态。例如,线程调用了Object.wait()Thread.join()LockSupport.park()方法。
  6. 超时等待(Timed Waiting):线程在等待其他线程执行特定操作,但最多等待指定的时间。例如,线程调用了Thread.sleep()Object.wait(long timeout)LockSupport.parkNanos()方法。
  7. 终止(Terminated):线程完成了run()方法的执行或因异常而终止,线程进入终止状态。

要获取线程的当前状态,可以调用Thread.getState()方法。

9. 线程优先级

在Java中,线程具有优先级。优先级较高的线程可能会比优先级较低的线程获得更多的执行时间。线程优先级是通过Thread.setPriority(int)方法设置的,取值范围为1到10,其中1表示最低优先级,10表示最高优先级。可以使用Thread.getPriority()方法获取线程的当前优先级。

需要注意的是,线程优先级并不能保证线程执行的顺序,它只是影响线程获得执行时间的可能性。因此,在编写多线程程序时,不应依赖线程优先级来实现正确的同步或顺序。

10. 守护线程

守护线程(Daemon Thread)是一种特殊类型的线程,主要用于在后台为其他线程提供服务。当所有非守护线程结束时,守护线程会自动结束。垃圾回收线程就是一个典型的守护线程。

要将线程设置为守护线程,可以在调用start()方法之前调用Thread.setDaemon(true)方法。可以使用Thread.isDaemon()方法检查线程是否为守护线程。

注意:不要将可能与其他线程进行交互的线程设置为守护线程,因为守护线程可能会在任何时候终止。如果守护线程在与其他线程交互时突然终止,可能会导致不可预测的行为或数据不一致。

11. 线程局部变量

线程局部变量(Thread-Local Variables)是一种特殊类型的变量,它为每个线程提供独立的变量副本。这意味着每个线程可以访问和修改它自己的变量副本,而不会影响其他线程的副本。

Java中提供了ThreadLocal类来实现线程局部变量。要创建一个线程局部变量,可以创建一个ThreadLocal对象,并调用get()set(T value)方法来获取和设置线程的变量副本。以下是一个使用ThreadLocal的示例:

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    int counter = threadLocalCounter.get();
                    threadLocalCounter.set(counter + 1);
                    System.out.println("Thread-1: " + threadLocalCounter.get());
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    int counter = threadLocalCounter.get();
                    threadLocalCounter.set(counter + 1);
                    System.out.println("Thread-2: " + threadLocalCounter.get());
                }
            }
        });

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

在这个示例中,threadLocalCounter是一个线程局部变量。我们为每个线程提供了初始值0。当每个线程执行时,它们都会访问和修改自己的计数器副本,而不会影响其他线程的副本。

12. 线程异常处理

当线程抛出未捕获的异常时,Java提供了一种机制来处理这些异常。可以通过实现Thread.UncaughtExceptionHandler接口并调用Thread.setDefaultUncaughtExceptionHandler()方法来设置全局的未捕获异常处理器。此外,还可以通过调用Thread.setUncaughtExceptionHandler()方法为单个线程设置未捕获异常处理器。

以下是一个设置未捕获异常处理器的示例:

public class ThreadExceptionHandlingExample {
    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + " throws exception: " + e);
            }
        });

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1 / 0);
            }
        }, "Thread-1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException("An exception occurred in Thread-2");
            }
        }, "Thread-2");

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

在这个示例中,我们设置了一个全局的未捕获异常处理器,用于处理所有线程抛出的未捕获异常。当Thread-1Thread-2抛出未捕获的异常时,异常处理器会捕获并处理这些异常。

13. 实践建议

在进行多线程编程时,以下是一些建议和注意事项:

  1. 尽量使用java.util.concurrent包中提供的高级并发工具类,而不是直接使用Threadsynchronized关键字。这可以简化代码并提高程序的可维护性和性能。
  2. 在编写多线程程序时,避免使用全局变量或共享状态,以降低同步的复杂性和可能出现的错误。
  3. 要充分利用Java的内存模型,确保线程之间正确地共享和同步数据。
  4. 使用线程池来管理和调度线程,以提高程序性能。
  5. 避免线程优先级来实现正确的同步或顺序,因为它不能保证线程执行的顺序。
  6. 使用守护线程为其他线程提供后台服务,但不要将可能与其他线程进行交互的线程设置为守护线程。
  7. 当需要为每个线程提供独立的变量副本时,使用线程局部变量。
  8. 为线程设置未捕获异常处理器,以便在线程抛出未捕获的异常时进行处理。

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

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

相关文章

vue - pc端实现对div的拖动功能

实现对div的拖动功能&#xff0c;需要先要知道以下的一些原生事件和方法&#xff1b; 1&#xff0c;事件: 方法描述onmousedown鼠标按钮被按下onmousemove鼠标被移动onmouseup鼠标按键被松开 2&#xff0c;方法: 方法描述event.clientX返回当事件被触发时鼠标指针相对于浏览…

【BIM+GIS】Supermap加载实景三维倾斜摄影模型

OSGB是常见的倾斜模型格式,本文讲述如何在Supermap中加载实景三维倾斜摄影模型OSGB。 文章目录 一、生成配置文件二、加载倾斜模型1. 新建场景2. 添加模型3. 高程调整一、生成配置文件 点击【三维数据】→【数据管理】→【生成配置文件】。 参数设置如下: 源路径:选择倾斜模…

【12.HTML入门知识-flexbox布局】

flexbox布局 1 认识flexbox2 flex布局的重要概念3 flex布局的模型4 flex container/items相关的属性4.1 flex-direction:主轴的方向决定布局方向4.2 flex-wrap 单行或者多行排列4.3 flex-flow 简写4.4 justify-content 主轴对齐方式4.5 align-item 交叉轴单行对齐方式4.6 align…

【数据结构】JDK HashMap源码解析

目录 &#x1f31f;HashMap源码解析 &#x1f308;类的属性 &#x1f308;构造方法 &#x1f308;put方法 &#x1f31f;对比常用Map的子类实现: &#x1f31f;HashMap源码解析 JDK8之前&#xff0c;HashMap就是数组链表&#xff1b; JDK8之后&#xff0c;变成了数组链表红…

基于C++/CLI实现C#与C++互调过程中的注意事项

目录 一、基于C/CLI 的调用原理二、注意事项如何基于VS2010完成C#调用C开发过程1、生成C应用程序&#xff08;非托管代码&#xff09;2、基于C/CLI生成托管代码3、C#调用 如何基于VS2010完成C调用C#开发过程 三、C/CLI与COM组件对比 一、基于C/CLI 的调用原理 C/CLI &#xff…

数据结构---队列的实现

文章目录 前言一、什么是队列&#xff1f;二、队列接口的实现 1.队列结构的定义2.接口实现总结 前言 队列是一种特殊的线性表。 特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&a…

【Vue.js】1668- 初中级前端必须掌握的 10 个 Vue 优化技巧

优化 Vue.js 应用性能是每个前端开发人员都需要关注的问题。本文我将分享 10 个初中级前端必须掌握的 Vue.js 优化技巧&#xff0c;无论您是正在学习 Vue.js&#xff0c;还是已经在应用开发中使用它&#xff0c;希望这些技巧都会对你的工作有所帮助。 1. 优雅的设置 v-for 中的…

Java 中常见的 Exception 有哪些

Java 是一种广泛使用的编程语言&#xff0c;它的强大和流行程度在很大程度上归功于它的异常处理机制。异常是在程序执行期间出现的错误或意外情况。在 Java 中&#xff0c;异常是通过抛出和捕获异常对象来处理的。在本文中&#xff0c;我们将介绍 Java 中的一些常见异常类型及其…

浅堆深堆解读

浅堆&#xff08;Shallow Heap&#xff09; 浅堆是指一个对象所消耗的内存。在32位系统中&#xff0c;一个对象引用会占据4个字节&#xff0c;一个int类型会占据4个字节&#xff0c;long型变量会占据8个字节&#xff0c;每个对象头需要占用8个字节。根据堆快照格式不同&#x…

【STM32】知识补充 深入探讨预分频器

【STM32】知识补充 深入探讨预分频器 概述分频器是什么工作原理计数器预分频器触发器预分频器模数计数器预分频器上升沿和下降沿 应用场景微控制器时钟预分频通信系统中的频率合成计时器与 PWM 波形生成数字电路设计中的同步与计时 预分频器实现方法硬件预分频器软件预分频器 案…

Swift之深入解析如何使用和自定义高级运算符

一、前言 在我之前的博客 Swift之深入解析如何自定义操作符 介绍了“基本运算符”&#xff0c;Swift 还提供了数种可以对数值进行复杂运算的高级运算符&#xff0c;它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。与 C 语言中的算术运算符不同&…

【flask】理解flask的几个难点,难啃的骨头,线程隔离啥的

1.三种路由和各自的比较 2.配置文件所有的字母必须大写 3.if __name__的作用 4.核心对象循环引用的几种解决方式–难 5.Flask的经典错误 6.上下文管理器 7.flask的多线程和线程隔离 三种路由 方法1&#xff1a;装饰器 python C#, java 都可以用这种方式 from flask import F…

SSM整合(三) | 异常处理器 - 项目异常的处理方案

文章目录 异常处理器异常处理器快速入门项目异常处理 异常处理器 异常处理器快速入门 程序开发过程中不可避免的会遇到异常现象 出现异常现象的常见位置与常见原因如下&#xff1a; 框架内部抛出的异常&#xff1a;因使用不规范导致 数据层抛出的异常&#xff1a;因外部服务器…

使用Statsmodel进行假设检验和线性回归

如果你使用 Python 处理数据&#xff0c;你可能听说过 statsmodel 库。Statsmodels 是一个 Python 模块&#xff0c;它提供各种统计模型和函数来探索、分析和可视化数据。该库广泛用于学术研究、金融和数据科学。在本文中&#xff0c;我们将介绍 statsmodel 库的基础知识、如何…

【技巧】Excel序号设置自动更新

做Excel表格的时候&#xff0c;我们经常需要设置序号&#xff0c;在输入序号的时候&#xff0c;你是不是这样做&#xff1f;手动输入序号1&#xff0c;再向下填充&#xff0c;当遇到有不想要的内容&#xff0c;点删除后&#xff0c;发现中间的序号就不连贯了&#xff0c;再手动…

二十五、OSPF高级技术——开销值、虚链路、邻居建立、LSA、多进程

文章目录 调试指令&#xff08;三张表&#xff09;1、邻居表&#xff1a;dis ospf peer brief2、拓扑表&#xff08;链路状态数据库&#xff09;&#xff1a;dis ospf lsdb3、路由表&#xff1a;dis ip routing-table 一、OSPF 开销值/度量值&#xff08;cost&#xff09;1、co…

代码审计笔记之开篇

思想 代码审计是从软件测试发展而来&#xff0c;早起一般采用常规软件测试与渗透测试的手段来发现源码漏洞&#xff0c;但是随着软件规模的越来越大&#xff0c;架构越来越复杂&#xff0c;安全漏洞和后门也越来越多越来越隐蔽&#xff0c;这使得传统的软件测试方法很难检出源…

【Java入门合集】第二章Java语言基础(一)

【Java入门合集】第二章Java语言基础&#xff08;一&#xff09; 博主&#xff1a;命运之光 专栏JAVA入门 学习目标 掌握变量、常量、表达式的概念&#xff0c;数据类型及变量的定义方法&#xff1b; 掌握常用运算符的使用&#xff1b; 掌握程序的顺序结构、选择结构和循环结构…

C/C++每日一练(20230501)

目录 1. 对链表进行插入排序 &#x1f31f;&#x1f31f; 2. 找出小于平均值的数 ※ 3. 二叉树的最大深度 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 对链表进行…

【五一创作】Apollo(入门)

Apollo(入门) Quick Start 配置中心是一种统一管理各种应用配置的基础服务组件 Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且…