Java多线程学习笔记

文章目录

  • 1. 引言
    • 1.1 多线程的重要性
  • 2. 什么是多线程
    • 2.1 线程的定义和基本概念
    • 2.2 线程与进程的区别
  • 3. 创建线程的方式
    • 3.1 继承Thread类
    • 3.2 实现Runnable接口,重写run方法
    • 3.3 实现Runnable接口,重写call方法
    • 3.4 匿名内部类创建Thread子类对象
    • 3.5 使用匿名内部类实现 Runnable 接口
    • 3.6 使用匿名内部类实现 Callable 接口
    • 3.7 实现runable和callable的区别
  • 4. 线程的生命周期
    • 4.1 线程的状态
    • 4.2 线程状态转换图
  • 5. 线程的基本控制方法
    • 5.1 线程状态管理
      • 5.1.1 start()
      • 5.1.2 join()
      • 5.1.3 sleep(long millis)
      • 5.1.4 yield():
    • 5.2 线程等待和唤醒
      • 5.2.1 wait()
      • 5.2.2 notify()
      • 5.2.3 notifyAll()
    • 5.3 线程优先级设置
      • 5.3.1 setPriority(int priority)
    • 5.4 线程中断
      • 5.4.1 interrupt():
      • 5.4.2 isInterrupted():
      • 5.4.3 interrupted():
    • start和run的区别
  • 6. 线程的同步与互斥
    • 6.1 synchronized关键字
    • 6.2 ReentrantLock
  • 7. 线程间通信
    • 7.1 BlockingQueue
  • 8. 高级多线程工具
    • 8.1 线程池
      • 8.1.1 线程池继承关系
      • 8.1.2 ThreadPoolExecutor
        • 8.2.2.1 线程池的任务调度流程
      • 8.1.2 Executors创建线程的4种方法
        • 1、newFixedThreadPool(int nThreads)
        • 2、newCachedThreadPool()
        • 3、newSingleThreadExecutor()
        • 4、newScheduledThreadPool(int corePoolSize)
    • 8.3 并发集合(ConcurrentHashMap, CopyOnWriteArrayList等)
    • 8.4 原子变量(AtomicInteger, AtomicBoolean等)
  • 10. 性能优化和最佳实践
    • 10.1 多线程性能优化
    • 10.2 多线程最佳实践
    • 示例
  • 11. 常见面试题


1. 引言

1.1 多线程的重要性

多线程编程是现代软件开发中的重要技术,能够提高资源利用率、提升应用程序的响应性、简化并发问题的建模和实现、提升性能,并增强系统的健壮性和容错性。掌握多线程编程技术,对开发高效、可靠的现代应用至关重要。

2. 什么是多线程

2.1 线程的定义和基本概念

在计算机科学中,是将进程划分为两个或多个线程(实例)或子进程,由单处理器(单线程)或多处理器(多线程)或多核处理系统并发执行。

2.2 线程与进程的区别

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是
一个进程从创建,运行到消亡的过程。

3. 创建线程的方式

3.1 继承Thread类

public class MyThread extends Thread {public void run() {System.out.println("Thread is running");}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
}

3.2 实现Runnable接口,重写run方法

class MyRunnable implements Runnable {public void run() {System.out.println("Thread is running");}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}}

3.3 实现Runnable接口,重写call方法

无参

public class MyCallable implements Callable<String> {public String call() {return "Thread is running";}public static void main(String[] args) {MyCallable callableTask = new MyCallable();String call = callableTask.call();System.out.println(call);}}

有参

public class MyCallable implements Callable<String> {private String name;public MyCallable(String name) {this.name = name;}public String call() {return "Hello, " + name;}public static void main(String[] args) {MyCallable callableTask = new MyCallable("Thread is running");String call = callableTask.call();System.out.println(call);}}

3.4 匿名内部类创建Thread子类对象

public class Test {public static void main(String[] args) {Thread thread = new Thread() {@Overridepublic void run() {System.out.println("Thread anonymous class is running");}};thread.start();}}

3.5 使用匿名内部类实现 Runnable 接口

public class Test {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Runnable anonymous class is running");}});thread.start();}}

3.6 使用匿名内部类实现 Callable 接口

public class Test {public static void main(String[] args) {// 创建一个线程池ExecutorService executor = Executors.newFixedThreadPool(1);// 提交一个Callable任务,使用匿名内部类实现Callable接口Future<String> future = executor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {System.out.println("Callable anonymous class is running");// 模拟一些长时间的任务Thread.sleep(1000);return "Callable task completed";}});try {// 获取Callable任务的执行结果String result = future.get();System.out.println("Result from Callable: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();} finally {// 关闭线程池executor.shutdown();}}}

3.7 实现runable和callable的区别

Runnable:适用于任务不需要返回结果或处理异常的场景,如简单的后台任务、事件处理等。
Callable:适用于任务需要返回结果或需要处理异常的场景,如计算任务、IO操作等。

4. 线程的生命周期

4.1 线程的状态

  1. 新建(New)
    (1)线程对象已经被创建,但还没有调用start()方法。
    (2)线程处于新建状态,还没有开始执行。

  2. 可运行(Runnable)
    (1)线程已经调用了start()方法,但还没有获得CPU时间片开始执行。
    (2)线程处于就绪状态,等待操作系统调度执行。
    (3)一旦获得CPU时间片,线程就会执行其run()方法。

  3. 运行中(Running)
    (1)线程当前正在执行代码。
    (2)严格来说,Java线程状态没有单独的“运行中”状态,它是Runnable状态的一部分。

  4. 阻塞(Blocked)
    (1)线程在等待监视器锁(monitor lock),以进入同步块或方法。
    (2)当线程试图进入一个被其他线程持有的同步块/方法时,会进入阻塞状态。

  5. 等待(Waiting)
    (1)线程等待另一线程显式地唤醒(通过Object.wait()、Thread.join()、LockSupport.park()等方法)。
    (2)等待状态下的线程不占用CPU时间片。

  6. 超时等待(Timed Waiting)
    (1)线程等待另一线程显式地唤醒,但有时间限制(通过Thread.sleep()、Object.wait(long timeout)、Thread.join(long millis)、LockSupport.parkNanos()等方法)。
    (2)超时等待状态下的线程不占用CPU时间片。

  7. 终止(Terminated)
    (1)线程已完成执行,或由于异常退出了run()方法。
    (2)线程生命周期结束,进入终止状态。

New -> Runnable -> Blocked/Waiting/Timed Waiting -> Runnable -> Terminated

4.2 线程状态转换图

在这里插入图片描述

5. 线程的基本控制方法

5.1 线程状态管理

5.1.1 start()

启动线程,使其进入可执行状态(RUNNABLE状态)。
系统会在后台为线程分配资源,并调用线程的run()方法执行任务。

5.1.2 join()

当前线程等待目标线程执行完成。
调用该方法的线程将会阻塞,直到目标线程执行完毕或超时。

5.1.3 sleep(long millis)

让当前线程休眠指定的时间(毫秒),进入TIMED_WAITING状态。
休眠结束或被中断后线程重新进入可运行状态。

5.1.4 yield():

暂停当前正在执行的线程对象,并执行其他线程。
可以让同优先级的线程有机会执行。

5.2 线程等待和唤醒

wait()、notify()、notifyAll():
这些方法是Object类中的方法,用于线程间的等待和唤醒机制,必须在同步代码块或同步方法中使用。

5.2.1 wait()

使当前线程等待,释放锁资源。

5.2.2 notify()

唤醒一个等待中的线程。

5.2.3 notifyAll()

唤醒所有等待中的线程。

5.3 线程优先级设置

5.3.1 setPriority(int priority)

设置线程的优先级,优先级范围为1到10,默认为5。
高优先级的线程具有抢占低优先级线程CPU时间的能力,但具体实现依赖于操作系统。

5.4 线程中断

5.4.1 interrupt():

中断线程,设置线程的中断状态为true。
被中断的线程可以通过检查自身的中断状态或捕获InterruptedException异常来处理中断请求。

5.4.2 isInterrupted():

判断线程是否被中断,返回true或false。

5.4.3 interrupted():

判断当前线程是否被中断,返回true或false,同时会清除中断状态。

start和run的区别

1. start() 方法
作用:

start()方法用于启动一个新的线程,并使线程进入可执行状态(RUNNABLE状态)。在调用start()方法后,系统会为该线程分配必要的资源,并执行线程的run()方法。start()方法只能被调用一次,重复调用会抛出IllegalThreadStateException异常。
执行过程:
当调用start()方法后,系统会在后台启动一个新的线程,并调用该线程的run()方法。start()方法返回后,当前线程(通常是调用start()的线程)会继续执行,而不会等待新线程的执行完毕。
2. run() 方法
作用:

run()方法是Thread类的实例方法,用于定义线程的主体任务或逻辑。当线程处于可执行状态并获得CPU时间片时,系统会调用线程的run()方法执行具体的任务。
执行过程:
run()方法被直接调用时,它会在当前线程中执行,而不会创建新的线程。直接调用run()方法会使得线程任务在当前线程中串行执行,不会体现出多线程的并发特性。

6. 线程的同步与互斥

一个多线程的程序,两个或者多个线程可能需要访问同一个数据资源。这时就必须考虑数据安全的问题,需要线程互斥或者同步。

6.1 synchronized关键字

例如对 count 进行修改或访问

同步方法:

public class Counter {private int count = 0;public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}

同步代码块:

public class Counter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized (lock) {count++;}}public int getCount() {synchronized (lock) {return count;}}
}

6.2 ReentrantLock

public class Counter {private int count = 0;private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public int getCount() {lock.lock();try {return count;} finally {lock.unlock();}}
}

7. 线程间通信

7.1 BlockingQueue

BlockingQueue 是 java.util.concurrent 包中的接口,它提供了线程安全的阻塞队列实现,如 ArrayBlockingQueue、LinkedBlockingQueue 等。它们简化了生产者-消费者模式的实现,自动处理线程间的等待和唤醒。

public static void main(String[] args) {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);Thread producer = new Thread(() -> {for (int i = 0; i < 10; i++) {try {queue.put(i);System.out.println("Produced: " + i);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});Thread consumer = new Thread(() -> {for (int i = 0; i < 10; i++) {try {int value = queue.take();System.out.println("Consumed: " + value);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});producer.start();consumer.start();
}

这里我只列举了其中一种,其他实现方式可参考:https://blog.csdn.net/qq_42411214/article/details/107767326 或其他文章。

8. 高级多线程工具

8.1 线程池

8.1.1 线程池继承关系

在这里插入图片描述

1、Executor 接口:Executor 是一个函数式接口,定义了一个执行任务的方法execute(Runnable command),用于将任务提交到线程池执行。它是所有执行器的基类,提供了最基本的任务执行方法。

2、ExecutorService :ExecutorService 是 Executor 的扩展,提供了更高级的任务管理功能,包括任务的提交、返回结果、取消任务以及关闭线程池等。它是一个接口,定义了管理线程池的方法。其中提供了一个方法submit(Runnable task):提交一个任务,返回 Future 对象。

3、AbstractExecutorService :AbstractExecutorService 是 ExecutorService 的抽象实现类,提供了一些默认实现,方便子类实现。它实现了 ExecutorService 接口,提供了 shutdown() 和 shutdownNow() 方法的默认实现。

4、ThreadPoolExecutor 类:ThreadPoolExecutor 是 ExecutorService 的一个具体实现,提供了一个可配置的线程池。
它具有高度的可配置性,可以设置线程池的核心线程数、最大线程数、线程存活时间、任务队列和拒绝策略等。

8.1.2 ThreadPoolExecutor

在这里插入图片描述

ThreadPoolExecutor 的构造方法包含七个参数:

1、核心线程数量——在线程池当中无论空闲多久都不会被删除的线程
2、线程池当中最大的线程数量——线程池当中最大能创建的线程数量
3、空闲时间(数值)——临时线程(线程池中出核心线程之外的线程)空闲了多久就会被淘汰的时间。
4、空闲时间(单位)——临时线程空闲了多久就会被淘汰的时间单位,要用枚举类TimeUnit类作为参数
5、阻塞队列——就是创建一个阻塞队列作为参数传入,就是当线程池当中线程数量已经达到了最大线程数量,允许多少个任务排队获取线程,其余的用参数七那个方案来处理。
6、创建线程的方式——不是new一个线程,而是传入一个线程工厂(例如:Executors工具类中的defaultThreadFactory方法返回的就是一个线程工厂)
7、要执行的任务过多时的解决方案——当等待队列中也排满时要怎么处理这些任务(任务拒绝策略)。

ThreadPoolExecutor 支持多种任务队列:

LinkedBlockingQueue:一个基于链表的阻塞队列,容量为 Integer.MAX_VALUE,适合大多数场景。
ArrayBlockingQueue:一个有界的阻塞队列,必须指定容量。
SynchronousQueue:一个不存储元素的队列,每个插入操作必须等待一个相应的移除操作。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

拒绝策略

当线程池和队列都满了时,可以使用拒绝策略来处理新提交的任务。ThreadPoolExecutor 提供了以下四种拒绝策略:

AbortPolicy(默认):抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程执行该任务。
DiscardPolicy:直接丢弃任务。
DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新提交被拒绝的任务。

8.2.2.1 线程池的任务调度流程

在这里插入图片描述

  1. 当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。
  2. 当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中,随着线程池 中的核
    心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取 任务并处
    理。
  3. 当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目 达到
    maximumPoolSize(最大线程数量设置值)。
  4. 如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任 务拒绝
    处理。

8.1.2 Executors创建线程的4种方法

1、newFixedThreadPool(int nThreads)

创建一个固定大小的线程池,线程数量为 nThreads。当所有线程都在忙时,新的任务会在队列中等待。

2、newCachedThreadPool()

创建一个缓存型线程池。如果线程池的规模超过了处理需求,会回收空闲线程;当需求增加时,会添加新的线程。

3、newSingleThreadExecutor()

创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,确保所有任务按顺序执行。

4、newScheduledThreadPool(int corePoolSize)

创建一个可以延迟或定期执行任务的线程池。

8.3 并发集合(ConcurrentHashMap, CopyOnWriteArrayList等)

线程安全的集合可参考:Java Collection集合介绍、fail-fast快速失败机制介绍、线程安全的容器介绍

8.4 原子变量(AtomicInteger, AtomicBoolean等)

AtomicInteger
AtomicInteger 是一个提供对 int 类型进行原子操作的类。它提供了许多方法来对整数进行原子操作,避免了使用 synchronized 关键字的复杂性和开销。

AtomicBoolean
AtomicBoolean 是一个提供对 boolean 类型进行原子操作的类。它同样提供了一些方法来确保在多线程环境下对布尔值的操作是线程安全的。

10. 性能优化和最佳实践

10.1 多线程性能优化

1、多线程性能优化合理使用线程池:
使用 Executor 框架来管理线程,而不是直接创建和管理线程。合理配置线程池的核心线程数、最大线程数和任务队列可以有效提高性能。
选择合适的线程池类型(如固定大小线程池、缓存线程池、单线程池、调度线程池)以适应具体需求。

2、减少上下文切换:
线程上下文切换开销很大,尽量减少不必要的线程切换。通过调整线程池大小来控制线程数量,避免过多的线程竞争CPU资源。
合理划分任务,避免任务过于细粒度导致频繁切换。

3、避免锁竞争:
尽量减少锁的使用范围和时间,缩小临界区范围。
使用 java.util.concurrent 包中的并发集合和工具类(如 ConcurrentHashMap、CopyOnWriteArrayList)来替代传统的同步集合。
使用 ReentrantLock 代替 synchronized,并结合 tryLock 方法进行非阻塞锁定。

4、无锁编程:
使用原子类(如 AtomicInteger、AtomicBoolean)进行无锁操作,提高性能。
尽量避免在高并发环境下使用锁,尝试使用无锁算法和数据结构。

10.2 多线程最佳实践

1、任务划分和线程数量:
合理划分任务,根据任务的性质和计算复杂度来确定线程的数量。
根据硬件资源和具体应用场景调优线程池的配置,一般来说,CPU密集型任务线程数应为 CPU 核心数+1,IO密集型任务线程数应大于CPU核心数。

2、线程安全:
确保共享资源的线程安全,使用适当的同步机制或并发集合。
尽量使用不可变对象,避免共享状态。

3、线程间通信:
使用高效的线程间通信机制,如 BlockingQueue、CountDownLatch、CyclicBarrier 等。

4、线程生命周期管理:
避免频繁创建和销毁线程,使用线程池来管理线程的生命周期。
在线程池使用完毕后,记得调用 shutdown() 方法来优雅地关闭线程池。

5、异常处理:
在线程内部进行适当的异常处理,避免因未处理的异常导致线程突然终止。
使用 Thread.setUncaughtExceptionHandler 来处理未捕获的异常。

6、性能监控和调优:
使用性能监控工具(如 JVisualVM、JProfiler)来监控线程的状态、锁竞争和CPU利用率。
定期进行性能调优,根据实际运行情况调整线程池配置和任务划分策略。

示例

public class Test {private static final int NUM_THREADS = 4;private static final int NUM_TASKS = 10;private static final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);private static final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();public static void main(String[] args) {for (int i = 0; i < NUM_TASKS; i++) {executor.submit(new Task("task" + i));}executor.shutdown();try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();}map.forEach((key, value) -> System.out.println(key + ": " + value));}static class Task implements Runnable {private final String taskName;Task(String taskName) {this.taskName = taskName;}@Overridepublic void run() {map.computeIfAbsent(taskName, k -> new AtomicInteger(0)).incrementAndGet();System.out.println(Thread.currentThread().getName() + " executed " + taskName);}}
}

11. 常见面试题

1、如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决?
2、线程有哪些基本状态?
3、为什么要创建线程池?创建线程池的方式?
4、创建线程池有哪几个核心参数? 如何合理配置线程池的大小?
5、说说阻塞队列的实现
6、线程间的通讯方式
7、说说线程安全问题,什么是线程安全,如何实现线程安全;
8、synchronized 和 ReentrantLock 的区别
9、线程安全的集合

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

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

相关文章

自定义 LLM:LangChain与文心一言擦出火花

自定义 LLM 自定义 LLM 需要实现以下必要的函数&#xff1a; _call &#xff1a;它需要接受一个字符串、可选的停用词&#xff0c;并返回一个字符串。 它还可以实现第二个可选的函数&#xff1a; _identifying_params &#xff1a;用于帮助打印 LLM 信息。该函数应该返回一…

如何在 Vue 3 中使用 vue3-print-nb 实现灵活的前端打印

你好&#xff0c;我是小白Coding日志&#xff0c;一个热爱技术的程序员。在这里&#xff0c;我分享自己在编程和技术世界中的学习心得和体会。希望我的文章能够给你带来一些灵感和帮助。欢迎来到我的博客&#xff0c;一起在技术的世界里探索前行吧&#xff01; 前言 在前端开…

Vue38-组件的几个注意点

一、组件回顾 1-1、创建组件 1-2、注册组件 1-3、使用组件 二、注意点&#xff1a;组件名 2-1、组件名一个单词&#xff1a;纯小写&#xff0c;或者&#xff0c;首字母大写 2-2、多个单词&#xff1a; 1、xx-bbbb 2、AaaBbbb&#xff1a;每个单词的首字母都大写 前提&…

【NUJ PA2】Read a Makefile

这里是NJU的PA2.2里面要求读懂的Makefile&#xff0c;是abstract-machine的。这里会放一些与读懂这个Makefile有关的知识。 下面是用ChatGPT解释的代码。只做大致的了解&#xff0c;写Makefile的时候还是要具体去看官方手册。 官方手册&#xff1a;make.pdf (gnu.org) # Makef…

Json-server 的使用教程

目录 前言一、简介二、安装与配置1. 安装 node-js2. npm 镜像设置3. 安装 json-server 三、使用1. 创建本地数据源2. 启动 Json Server3. 操作数据&#xff08;1&#xff09;查询数据&#xff08;2&#xff09;新增数据&#xff08;3&#xff09;修改数据&#xff08;4&#xf…

RTOS笔记--资源管理

资源管理 资源管理&#xff0c;其实就是前面介绍过的通知方式中的队列信号量互斥量等是如何访问临界资源的&#xff0c;如何做到完全互斥。 在之前举过一个例子&#xff1a;当我们使用全局变量来进行互斥操作时&#xff0c;有可能在改写全局变量时被切换使得不再互斥&#xff0…

【SpringBoot】深入分析 SpringApplication 源码:彻底理解 SpringBoot 启动流程

在黄昏的余晖里&#xff0c;梦境渐浓&#xff0c;如烟如雾。心随星辰&#xff0c;徜徉远方&#xff0c;岁月静好&#xff0c;愿如此刻般绵长。 文章目录 前言一、SpringBoot 应用二、SpringApplication2.1 SpringApplication 中的属性2.2 SpringApplication 的构造器2.3 Sprin…

【Linux】基础IO——文件描述符,重定向,FILE

话接上篇&#xff1a; 1.文件描述符fd 磁盘文件 VS 内存文件&#xff1f; 当文件存储在磁盘当中时&#xff0c;我们将其称之为磁盘文件&#xff0c;而当磁盘文件被加载到内存当中后&#xff0c;我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程…

JVM 三色标记算法

三色标记算法核心原理 三色标记算法是一种JVM的垃圾标记算法&#xff0c;CMS/G1垃圾回收器就是使用的这种算法&#xff0c;它可以让JVM在不发生或者尽可能短的发生STW&#xff08;Stop The World&#xff09;的情况下进行垃圾的标记和清除。 顾名思义&#xff0c;三色标记算法…

实现JWT认证与授权的Spring Boot项目详解

我们将详细介绍如何使用JWT&#xff08;JSON Web Tokens&#xff09;结合Spring Boot框架实现用户认证和授权系统。此方案将包括用户注册、登录以及通过JWT令牌进行后续请求的身份验证过程。我们将从引入必要的依赖开始&#xff0c;然后逐步构建项目的各个部分&#xff0c;包括…

精品丨PowerBI迁移到SSAS

业务场景&#xff1a; 企业初期在进行 BI 可视化路线的时候&#xff0c;往往不会选择方案较为完整的SSAS&#xff0c;而是会选择轻量的 PowerBI 方案&#xff0c;究其根本还是软件成本的问题。 但是随着模型越来越臃肿&#xff0c;维护成本越来越高&#xff0c;有很多模型需要进…

Java面向对象-抽象类和抽象方法

Java面向对象-抽象类和抽象方法 1、代码案例展示2、抽象类和抽象方法的关系&#xff1a; 1、代码案例展示 1、在一个类中会有一类方法&#xff0c;无需重写&#xff0c;直接使用 2、在一个类中会有一类方法&#xff0c;会对这个方法进行重写 3、一个方法的方法体去掉&#xff…

【文心智能体分享】日记周报助手

引言 在繁忙的实习生活中&#xff0c;你是否曾为如何整理日常的工作日志、周报、月报而烦恼&#xff1f;现在&#xff0c;我们为你带来了一个全新的智能体——“日记周报助手”&#xff0c;它将成为你实习过程中的得力助手&#xff0c;帮你轻松整理实习日志&#xff0c;让你的…

mysql 中的锁

一.锁的介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff0c;在数据库中&#xff0c;除了传统的计算资源&#xff08;cpu&#xff0c;ram&#xff0c;i/o&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性…

初见 Rollup 的十大常见问题

文章目录 初见 Rollup 的十大常见问题1. 超神奇的 Rollup 英文解释&#xff01;2. 为什么 ESM 要比 CommonJS 要好呢&#xff1f;3. 什么是 tree-shaking ?4. 如何使用 Rollup 处理 CommonJS&#xff1f;5. 为什么 node-resolve 不是一个内置功能&#xff1f;6. 为什么在进行代…

如何警用root用户登录ssh

使用tail指令&#xff0c;可以动态查看日志信息。 &#xff08;tail -f /var/log/secure或messages&#xff09; 使用>符号&#xff0c;可以清空日志内容&#xff0c;不删除文件本身。 禁用root用户为以下步骤&#xff1a; 首先使用useradd创建用户&#xff08;可以修改为其…

STM32HAL-最简单的时间片论法

目录 概述 一、开发环境 二、STM32CubeMx配置 三、编码 四、运行结果 五、总结 概述 本文章使用最简单的写法时间片论法框架,非常适合移植各类型单片机,特别是资源少的芯片上。接下来将在stm32单片机上实现,只需占用1个定时器作为tick即可。(按键框架+时间片论法)…

【数据结构之B树的讲解】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【乐吾乐2D可视化组态编辑器】开关、阀门、报警状态切换

开关状态 开关的断开与闭合&#xff1a;将电力组件的“开”与“关”2个组件重叠在一起&#xff0c;右键选择“组合为状态”&#xff0c;属性面板中就可以任意切换状态。 视频教程&#xff1a;开关阀门多状态控制 乐吾乐2D可视化组态编辑器地址&#xff1a;https://2d.le5le.co…

【python】python指南(三):使用正则表达式re提取文本中的http链接

一、引言 对于算法工程师来说&#xff0c;语言从来都不是关键&#xff0c;关键是快速学习以及解决问题的能力。大学的时候参加ACM/ICPC一直使用的是C语言&#xff0c;实习的时候做一个算法策略后台用的是php&#xff0c;毕业后做策略算法开发&#xff0c;因为要用spark&#x…