Java 并发编程

Java 并发编程

  • 一、线程创建
    • 1.继承 Thread 类
    • 2.实现 Runnable 接口
    • 3.实现 Callable 接口
  • 二、线程方法
  • 三、线程同步
    • 1.同步代码块
    • 2.同步方法
    • 3.ReentrantLock
    • 4.乐观锁
  • 四、线程池
    • 1.ThreadPoolExecutor
    • 2.Executors

在这里插入图片描述

一、线程创建

1.继承 Thread 类

通过继承 Thread 类来创建线程是最简单的方法之一。只需要创建一个继承自 Thread 的子类,并重写其 run() 方法,然后通过调用子类的 start() 方法来启动线程。

这种方法的优点是简单易用,适用于简单的线程逻辑。不过由于 Java 不支持多重继承,因此通过继承 Thread 类来创建线程会限制类的继承关系。

package atreus.ink;

import java.lang.management.ManagementFactory;

public class MyThread extends Thread {
    @Override
    public void run() {
        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}
package atreus.ink;

import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");

    }
}
My pid is 66201, my tid is 13.
My pid is 66201, my tid is 1.

注意事项:主线程启动子线程需要调用 start() 方法而不是 run() 方法,调用 run() 方法会将线程对象当作普通的 Java 对象来进行方法调用,并不会向操作系统注册线程,实际还是单线程执行。


2.实现 Runnable 接口

通过实现 Runnable 接口来创建线程是更加灵活的方法。通过这种方式,一个类既可以实现其他接口,又可以创建线程。

package atreus.ink;

import java.lang.management.ManagementFactory;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}
package atreus.ink;

import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        new Thread(runnable).start();

        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}
My pid is 7388, my tid is 14.
My pid is 7388, my tid is 1.

此外,对于通过实现 Runnable 接口的创建方法,还可以通过匿名内部类和 Lambda 进行代码简化。

package atreus.ink;

import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) {
        // 1.通过匿名内部类进行代码简化
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                long threadId = Thread.currentThread().getId();
                String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
                System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
            }
        };
        new Thread(runnable).start();

        // 2.通过Lambda表达式进行简化
        new Thread(() -> {
            long threadId = Thread.currentThread().getId();
            String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
            System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
        }).start();

        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}

3.实现 Callable 接口

Callable 接口类似于 Runnable 接口,但它可以返回一个值,并且可以抛出异常。线程通过 ExecutorServicesubmit 方法来调用 Callable,并返回一个 Future 对象。

package atreus.ink;

import java.lang.management.ManagementFactory;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        return "My pid is " + processId + ", my tid is " + threadId + ".";
    }
}
package atreus.ink;

import java.lang.management.ManagementFactory;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new MyCallable();
        FutureTask<String> task = new FutureTask<>(callable);
        new Thread(task).start();
        System.out.println(task.get());  // get方法会使主线程等待子线程执行完毕,然后获取结果

        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}
My pid is 25580, my tid is 14.
My pid is 25580, my tid is 1.

当然,这种创建方法也可以通过匿名内部类和 Lambda 表达式简化。

package atreus.ink;

import java.lang.management.ManagementFactory;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(() -> {
            long threadId = Thread.currentThread().getId();
            String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
            return "My pid is " + processId + ", my tid is " + threadId + ".";
        });
        new Thread(task).start();
        System.out.println(task.get());

        long threadId = Thread.currentThread().getId();
        String processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("My pid is " + processId + ", my tid is " + threadId + ".");
    }
}

二、线程方法

Thread 类提供了很多与线程操作相关的方法:

常用方法说明
public static Thread currentThread()获取当前执行的线程对象
public void run()线程的任务方法
public void start()启动线程
public String getName()获取当前线程的名称,线程名称默认是 Thread-索引
public void setName(String name)为线程设置名称
public static void sleep(long time)让当前执行的线程休眠
public final void join()调用这个方法的线程将等待被调用的线程执行完成,然后再继续执行,类似于 POSIX 线程库中的 pthread_join
package atreus.ink;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        });

        thread.start();
        thread.join();  // 主线程会在此处等待子线程执行完毕

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

三、线程同步

1.同步代码块

同步代码块是通过使用 synchronized 关键字来实现的,它可以用来保护代码块,确保在同一时刻只有一个线程能够进入同步代码块。一个典型的用法是将需要同步的代码放在同步代码块中,并指定一个锁对象作为同步的依据。

虽然任意一个唯一的对象(比如字符串)都可以作为同步代码块的锁对象,但锁的粒度过大或过小都会导致并发安全问题。对于实例方法,通常使用 this 作为锁对象,对于静态方法,通常使用类的字节码对象 类名.class 作为锁对象。

package atreus.ink;

public class Main {
    private static int counter;
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                // 同步代码块
                synchronized (lock) {
                    counter++;
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}
Final counter value: 20000

此外,在同步代码块中还可以通过 wait() 方法让当前线程进入等待状态,直到其他线程调用相同对象的 notify()notifyAll() 方法来唤醒它。

package atreus.ink;

public class Main {
    public static void main(String[] args) {
        final Object lock = new Object();

        // 等待线程
        Thread waiter = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Waiter: Waiting for a notification...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Waiter: Got a notification!");
            }
        });

        // 通知线程
        Thread notifier = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Notifier: Performing some work...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Notifier: Work done, notifying the waiter...");
                lock.notify();
            }
        });

        waiter.start();
        notifier.start();
    }
}
Waiter: Waiting for a notification...
Notifier: Performing some work...
Notifier: Work done, notifying the waiter...
Waiter: Got a notification!

2.同步方法

同步方法是通过在方法声明中使用 synchronized 关键字来实现的,它可以将整个方法体都变成一个同步代码块。同步方法底层通过隐式锁对象实现,只是锁的范围是整个方法代码。如果方法是实例方法,同步方法默认用 this 作为的锁对象。如果方法是静态方法,同步方法默认用 类名.class 作为的锁对象。

同步方法的优点是简单,可以很方便地实现线程同步。不过锁的范围较大,可能影响性能,因为其他不需要同步的代码也会被锁住。

package atreus.ink;

public class Main {
    private static int counter;

    public static synchronized void increment() {
        for (int i = 0; i < 10000; i++) {
            counter++;
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(Main::increment);
        Thread thread2 = new Thread(Main::increment);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}
Final counter value: 20000

3.ReentrantLock

ReentrantLock 是 Java 提供的一个可重入锁,它相比于使用 synchronized 关键字具有更大的灵活性。通过 ReentrantLock,你可以显式地获取锁和释放锁,从而精确地控制同步范围。

ReentrantLock 提供了更多的功能,比如可重入性、可定时的锁等待、公平性设置等。但需要注意,使用 ReentrantLock 需要手动释放锁,因此务必在 finally 块中释放锁,以防止死锁情况的发生。

package atreus.ink;

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

public class Main {
    private static final Lock lock = new ReentrantLock();
    private static int counter;

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                lock.lock();
                try {
                    counter++;
                } finally {
                    // 放在finally块中保证锁一定能被释放
                    lock.unlock();
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}
Final counter value: 20000

4.乐观锁

ReentrantLock、同步代码块和同步方法都是基于悲观锁思想实现的,意味着它们假定在执行临界区代码期间会发生并发冲突。在高并发场景下,由于激烈的锁竞争,可能会导致线程阻塞,从而降低性能。特别是在多读场景下,悲观锁可能引入大量的额外并发开销,因为每个读操作都需要获得独占锁。

相比之下,乐观锁的思想更适合多读场景。乐观锁假定数据操作不存在并发冲突,因此不会引起锁竞争,也不会导致线程阻塞和死锁。乐观锁通常在提交修改时才验证资源是否被其他线程修改。然而,在多写场景下,乐观锁可能会由于频繁的冲突而引起失败和重试,这可能会对性能产生一定的影响。不过在多写场景下乐观锁会频繁失败和重试,这同样会对性能造成一定影响。

java.util.concurrent.atomic 包下面 AtomicIntegerAtomicLongAtomicIntegerArrayAtomicReference 等原子变量类均基于乐观锁的思想实现。

package atreus.ink;

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                counter.incrementAndGet();
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter.get());
    }
}
package atreus.ink;

import java.util.concurrent.atomic.AtomicReference;

public class Main {
    private static final AtomicReference<Integer> counterRef = new AtomicReference<>(0);

    public static void main(String[] args) {
        Runnable incrementTask = () -> {
            for (int i = 0; i < 10000; i++) {
                while (true) {
                    Integer current = counterRef.get();
                    Integer updated = current + 1;
                    if (counterRef.compareAndSet(current, updated)) {
                        break;
                    }
                }
            }
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counterRef.get());
    }
}
Final counter value: 20000

四、线程池

1.ThreadPoolExecutor

Java 中的线程池接口为 ExecutorService,一个常用的实现类为 ThreadPoolExecutor,其构造函数为:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                   long keepAliveTime, TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   ThreadFactory threadFactory,
                   RejectedExecutionHandler handler)

参数说明

  • corePoolSize线程池的核心线程数,即任务队列未达到队列容量时,最大可以同时运行的线程数量。即使线程是空闲的,它们也不会被销毁,除非线程池被关闭。
  • maximumPoolSize线程池的最大线程数。在没有核心线程空闲的情况下,如果任务数量增加,线程池可以扩展到最大线程数。如果任务数量继续增加,超过线程池最大大小的任务将会被拒绝执行。
  • keepAliveTime非核心线程的最大空闲时间。当线程池中的线程数量超过 corePoolSize,多余的非核心线程会在空闲时间超过 keepAliveTime 后被销毁,以减少资源占用。
  • unit时间单位,用于指定 keepAliveTime 的时间单位。
  • workQueue用于存储等待执行的任务的阻塞队列。当所有核心线程都忙碌时,新任务将被放入队列等待执行。常用的队列类型包括 LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueue 等。
  • threadFactory用于创建线程的工厂。可以通过提供自己实现的 ThreadFactory 自定义线程的创建过程。
  • handler拒绝策略,用于处理无法提交给线程池执行的任务。当任务数量超过线程池最大大小且队列已满时,将使用拒绝策略处理任务。常见的策略有 AbortPolicyCallerRunsPolicyDiscardPolicy 等。

注意事项

  • 新任务提交时发现核心线程都在忙任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
  • 核心线程和临时线程都在忙任务队列也满了,新的任务过来的时候才会开始拒绝任务

常用方法

方法名称说明
void execute(Runnable command)执行 Runnable 任务
Future<T> submit(Callable<T> task)执行 callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown()等全部任务执行完毕后,再关闭线程池
List<Runnable> shutdownNow()立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
package atreus.ink;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.printf("[%s] %s\n", Thread.currentThread().getName(),
                          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
package atreus.ink;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(2, 3,
                                                      8, TimeUnit.SECONDS,
                                                      new ArrayBlockingQueue<>(2),
                                                      Executors.defaultThreadFactory(),
                                                      new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();

        pool.execute(target);  // 核心线程
        pool.execute(target);  // 核心线程
        pool.execute(target);  // 任务队列等待
        pool.execute(target);  // 任务队列等待
        pool.execute(target);  // 任务队列满,启动一个临时线程
        pool.execute(target);  // 核心线程和临时线程忙,同时任务队列已满,拒绝任务

        pool.shutdown();
    }
}
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task atreus.ink.MyRunnable@7a0ac6e3 rejected from java.util.concurrent.ThreadPoolExecutor@71be98f5[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
	at atreus.ink.Main.main(Main.java:20)
[pool-1-thread-2] 2023-08-30 15:57:44
[pool-1-thread-1] 2023-08-30 15:57:44
[pool-1-thread-3] 2023-08-30 15:57:44
[pool-1-thread-1] 2023-08-30 15:57:47
[pool-1-thread-2] 2023-08-30 15:57:47

新任务拒绝策略

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出 RejectedExecutionException 异常,是默认的策略
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常,这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的 run() 方法从而绕过线程池直接执行

2.Executors

Executors 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

方法名称说明
public static ExecutorService newFixedThreadPool(int nThreads)创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它
public static ExecutorService newSingleThreadExecutor()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了 60s 则会被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务或者定期执行任务
package atreus.ink;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.printf("[%s] %s\n", Thread.currentThread().getName(),
                          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}
package atreus.ink;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        Runnable target = new MyRunnable();

        // 延迟1秒后执行target任务
        pool.schedule(target, 1, TimeUnit.SECONDS);

        // 延迟2秒后,每隔3秒执行一次target任务
        pool.scheduleAtFixedRate(target, 2, 3, TimeUnit.SECONDS);

        Thread.sleep(10 * 1000);
        pool.shutdown();
    }
}
[pool-1-thread-1] 2023-08-30 16:26:33
[pool-1-thread-2] 2023-08-30 16:26:34
[pool-1-thread-2] 2023-08-30 16:26:37
[pool-1-thread-2] 2023-08-30 16:26:40

参考:

https://www.bilibili.com/video/BV1Cv411372m/?p=175
https://javaguide.cn/java/concurrent/

在这里插入图片描述

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

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

相关文章

【高性能计算】opencl语法及相关概念(三)事件,内存

opencl中的事件概念 当谈到OpenCL中的事件时&#xff0c;它们代表了执行的各个阶段或操作的状态信息。通过使用事件&#xff0c;您可以跟踪和管理内核执行以及内存操作的进度和顺序。以下是与OpenCL事件相关的关键概念&#xff1a; 创建事件&#xff1a;您可以使用clCreateUse…

Java运行时jar时终端输出的中文日志是乱码

运行Jar时在控制台输出的中文日志全是乱码&#xff0c;这是因为cmd/bash默认的编码是GBK&#xff0c;只要把cmd的编码改成UTF-8即可 两种方式修改&#xff1a;临时修改和注册表永久修改 临时修改 只对当前的cmd页面有效&#xff0c;关闭后重新打开都会恢复成GBK, 打开cmd&am…

记一次对链接、COMMON块、多重符号定义的理解

问题引入 首先是两个测试程序 // foo.c long long int a;// bar.c #include <stdio.h>int a; int main(){a 1;long long int len sizeof(a);printf("%lld\n", len);return 0; }将两个程序链接到一起 问题&#xff1a;len等于几&#xff1f; 初步分析 环境…

研磨设计模式day15策略模式

场景 问题描述 经常会有这样的需要&#xff0c;在不同的时候&#xff0c;要使用不同的计算方式。 解决方案 策略模式 定义&#xff1a; 解决思路&#xff1a;

PyCharm软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 PyCharm是一种集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门为Python开发者设计。它是由捷克软件公司JetBrains开发的&#xff0c;为Python开发人员提供了高效、易用和功能丰富的工具集。 以下是PyCharm软件的主要…

基于 Docker 的 MySQL 主从复制搭建(Mac M1版本)

系统&#xff1a;Macbook M1 镜像版本&#xff1a;mysql:5.7 如果是要查 slave连接不上 master的问题&#xff0c;可以直接跳到文章末尾踩坑处 准备工作 拉取镜像 docker pull mysql:5.7本地数据卷挂载 因为mysql不挂载的话&#xff0c;重启丢失数据&#xff0c;所以在本地创…

用Cmake build OpenCV后,在VS中查看OpenCV源码的方法(环境VS2022+openCV4.8.0) Part III

用Cmake build OpenCV后&#xff0c;在VS中查看OpenCV源码的方法(环境VS2022openCV4.8.0) Part III 用Cmake build OpenCV后&#xff0c;在VS中查看OpenCV源码的方法&#xff08;环境VS2022openCV4.8.0&#xff09; Part I_松下J27的博客-CSDN博客 用Cmake build OpenCV后&…

Maven的profiles多环境配置

一个项目通常都会有多个不同的运行环境&#xff0c;例如开发环境&#xff0c;测试环境、生产环境等。而不同环境的构建过程很可能是不同的&#xff0c;例如数据源配置、插件、以及依赖的版本等。每次将项目部署到不同的环境时&#xff0c;都需要修改相应的配置&#xff0c;这样…

学习node之——如何在项目中使用MySQL、前后端的身份认证

上一篇文章只写了一丢丢&#xff0c;这篇才是正片&#xff0c;look look look 一、使用mysql模块操作数据库 1、查询数据 这里连接数据库的用户和密码都是我们在安装mysql时配置的密码。每个人的users表格里面数据不同&#xff0c;结果也会不一样哟&#xff01; // 导入mys…

统一网关Gateway

文章目录 概览网关的作用搭建网关断言工厂路由过滤器全局过滤器案例 过滤器执行顺序跨域问题 概览 网关的作用 搭建网关 断言工厂 路由过滤器 全局过滤器 案例 过滤器执行顺序 跨域问题

QT基础教程之四QMainWindow

QT基础教程之四QMainWindow QMainWindow是一个为用户提供主窗口程序的类&#xff0c;包含一个菜单栏&#xff08;menu bar&#xff09;、多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget)&#xff0c;是许多应用程序…

【c语言】输出n行按如下规律排列的数

题述&#xff1a;输出n行按如下规律排列的数 输入&#xff1a; 4(应该指的是n) 输出: 思路&#xff1a; 利用下标的规律求解&#xff0c;考察数组下标的灵活应用&#xff0c;我们可以看出数从1开始是斜着往下放的&#xff0c;那么我们如何利用两层for循环求解这道题&#xff…

打造完美直播:深入解析人脸美颜SDK的算法与特性

在如今数字化的时代&#xff0c;直播已经成为了人们与世界互动的重要方式之一。而在众多直播平台中&#xff0c;吸引观众并提供高质量的视觉体验成为了至关重要的任务。其中&#xff0c;人脸美颜技术的应用对于提升直播质量和观众体验起到了不可忽视的作用。本文将深入解析人脸…

【pyqt5界面化工具开发-13】QtDesigner功能择优使用

目录 0x00 前言&#xff1a; 一、完成基本的布局 二、其他功能的使用 三、在代码行开发 0x00 前言&#xff1a; QtDesigner工具的择优使用&#xff1a; 1、他的界面开发&#xff0c;是我们主要需要使用的功能 2、他的其他功能的使用&#xff0c;有需要就可使用&#xff…

【java】【已解决】IDEA启动报错:Lombok Requires Annotation Processing

解决办法&#xff1a; 1、根据异常提示操作&#xff1a; 直接点击错误提示后面的蓝色标识【Enable】&#xff08;小编点完了所以变灰色&#xff09;&#xff0c;此操作等价于下面的步骤&#xff1a; 【File】-->【Settings】-->【Build】-->【Compiler】-->【Ann…

42、Flink 的table api与sql之Hive Catalog

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

校园用电安全管理系统可以识别违规电器吗

校园用电安全管理系统是处理恶意用电问题有效手段之一&#xff0c;系统具有实时监测、异常预警、监测设备运行状态、远程控制用电等功能&#xff0c;可以从根本上管理学校用电量&#xff0c;制定合理的用电计划&#xff0c;限制用电成本&#xff0c;避免各种恶意用电行为&#…

单片机学习-蜂鸣器如何发出声音

硬件电路 软件编写 ①发出声音 #include "reg52.h" typedef unsigned int u16; // 重新定义 类型 typedef unsigned char u8; // 重新定义 类型sbit BEEP P2^5; //定义 P2第五个管教 为BEEP // 延时函数 void delay_time(u16 times) {while(times--); } vo…

2022年下半年系统架构设计师真题(下午带答案)

试题一 (25分) 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管理方式的灵活性&#xff0c;由于当前用户规模不大&#xff0c;业务也相对…

Mac版JFormDesigner IDEA插件安装(非商业用途)

前言 仅供个人开发者使用&#xff0c;勿用作商业用途。 仅供个人开发者使用&#xff0c;勿用作商业用途。 仅供个人开发者使用&#xff0c;勿用作商业用途。 感觉做了这些年开发&#xff0c;怎么感觉市场越搞越回去了。桌面应用又成主流了&#xff1f; 甲方让做桌面客户端&am…