Java中的并发编程模型和常用工具类
本文主要介绍了Java中的并发编程模型和常用工具类,首先阐述了并发编程的概念及其重要性,然后详细介绍了线程的基本概念、生命周期和状态转换、同步与互斥、死锁问题以及线程池的使用和实现原理。接着介绍了synchronized关键字和Lock接口的使用、原子变量和原子操作类的使用、Condition接口和ReentrantLock类的使用、CountDownLatch类和CyclicBarrier类的使用、Semaphore类和Exchanger类的使用。最后,提出了并发编程的性能优化和注意事项。
一. 引言
1.1 并发编程的概念及重要性
并发编程指的是在多核心、多线程、多任务的操作系统中,同时执行多个任务和线程。在计算机领域中,如今大多数系统都支持并发编程,因为并发编程可以大大提高系统的吞吐量和响应速度,提升系统的性能和可用性。Java作为一门面向对象的编程语言,也提供了一套完善的并发编程模型和工具类库,为Java开发者提供了便捷的并发编程解决方案。
1.2 Java中的并发编程模型和常用工具类简介
Java中的并发编程模型基于线程和锁机制。Java中的线程是轻量级的进程,每个线程都有自己的执行路径,可以独立执行任务。Java中的锁机制可以保证多个线程之间的同步和互斥,避免资源竞争和冲突。Java中的线程和锁机制是Java并发编程的基础。
- synchronized关键字和Lock接口的使用 synchronized关键字和Lock接口是Java中的两种锁机制,它们可以保证多个线程之间的同步和互斥。synchronized关键字是Java中最常用的同步机制,它可以用来修饰方法或代码块。Lock接口是Java中提供的另一种同步机制,它可以实现更细粒度的锁定,支持可中断、可重入等高级特性。
- 原子变量和原子操作类的使用 Java中的原子变量和原子操作类可以保证数据的原子性,避免多线程之间的竞争和冲突。Java中的原子操作类包括AtomicInteger、AtomicLong、AtomicBoolean等,它们都提供了一系列原子操作方法,例如incrementAndGet、decrementAndGet等,可以实现原子性的数值计算。
- Condition接口和ReentrantLock类的使用 Condition接口和ReentrantLock类是Java中用于高级同步机制的工具类。Condition接口是ReentrantLock类的一部分,可以用来实现更高级别的条件等待和通知机制。ReentrantLock类是Java中提供的另一种同步机制,可以实现更细粒度的锁定和高级特性,例如可重入、公平性、可中断等。
- CountDownLatch类和CyclicBarrier类的使用 CountDownLatch类和CyclicBarrier类是Java中用于同步多线程的工具类。
二. Java中的并发编程模型
2.1 进程和线程的基本概念
进程和线程是计算机操作系统中的两个基本概念,它们都可以执行任务和程序,但是在实现方式、资源占用、通信方式等方面有所不同。
1. 进程
进程是计算机中执行任务的基本单位,它是操作系统中的一个独立的运行实例。每个进程都有自己的独立地址空间、代码段、数据段、堆栈、文件描述符等系统资源。进程之间相互独立,互不干扰。进程是资源分配的最小单位,它可以分配和使用计算机中的资源,如CPU、内存、文件、网络等。
2. 线程
线程是进程中的一个执行单元,它是操作系统中调度的最小单位。一个进程中可以包含多个线程,这些线程共享进程的地址空间和系统资源,如文件、网络等。线程之间可以通过共享内存的方式进行通信,但是也可能出现竞争和冲突的情况。线程是轻量级的进程,创建和销毁线程的开销相对较小,因此可以更加高效地利用计算机的资源。
3. 进程和线程的区别
进程和线程的主要区别在于资源占用、调度和通信方式等方面。进程是资源分配的最小单位,它可以分配和使用计算机中的资源,但是进程之间的通信需要通过IPC(Inter-Process Communication)机制,开销相对较大。线程是轻量级的进程,它共享进程的地址空间和系统资源,因此线程之间的通信和调度开销相对较小。但是线程之间可能出现竞争和冲突的情况,需要进行同步和互斥操作。
2.2 线程的生命周期和状态转换
线程的生命周期包括五种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。这些状态可以通过不同的方法进行转换,下面是每个状态的含义、转换条件:
- 新建状态(New) 线程被创建但是还没有开始运行的状态,此时线程并没有分配到CPU资源,只是一个空壳子。新建状态的线程可以通过start()方法启动。
- 就绪状态(Runnable) 线程被分配到CPU资源并且可以运行的状态,但是并不一定正在执行,等待CPU调度。就绪状态的线程可以通过线程调度器进行调度,进入运行状态。
- 运行状态(Running) 线程正在执行的状态,正在占用CPU资源,执行任务。运行状态的线程可以通过sleep()、yield()、wait()等方法进入阻塞状态,也可以通过线程调度器的调度进入就绪状态。
- 阻塞状态(Blocked) 线程因为某些原因无法执行任务,暂时放弃CPU资源的状态。阻塞状态包括多种类型:等待阻塞(wait)、同步阻塞(synchronized)、其他阻塞(sleep、join、park等)。当阻塞状态结束后,线程可以重新进入就绪状态等待CPU调度。
- 死亡状态(Dead) 线程执行完任务或者被强制终止,线程生命周期结束的状态。死亡状态的线程无法再次进入其他状态。
参考文章:https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html
2.3 线程的同步与互斥
线程同步是指多个线程在共享数据时按照一定的规则进行访问和操作,以避免数据的混乱和错误。线程互斥是指多个线程在对共享数据进行访问时,通过一些机制防止多个线程同时对数据进行操作,从而避免数据的冲突和错误。
在Java中,线程同步和互斥是通过锁机制实现的。锁是一个标识,用来保护共享资源,只有持有锁的线程才能访问共享资源。Java提供了两种锁机制:synchronized关键字和Lock接口。
synchronized关键字是Java中最基本的锁机制,它是一种隐式锁,只需要在方法或者代码块前加上synchronized关键字即可实现同步和互斥。synchronized关键字的作用是确保同一时间只有一个线程可以进入同步代码块或方法,并且在执行完同步代码块或方法后会自动释放锁。
Lock接口是Java中的另一种锁机制,它是一种显示锁,需要手动加锁和释放锁。Lock接口提供了更加灵活和精细的锁控制,比如可以设置超时时间、多个条件变量等。
线程同步和互斥可以有效避免多个线程对共享数据的冲突和错误,保证程序的正确性和稳定性。但是如果同步和互斥使用不当,也会带来一定的性能问题,因此需要在使用时考虑好锁的粒度、锁的持有时间和锁的竞争情况等因素。
2.4 线程的死锁问题
线程死锁是指在多线程并发的程序中,两个或多个线程因为相互等待对方释放资源而陷入一种无限等待的状态,从而导致程序无法继续执行的问题。简单来说,就是两个或多个线程互相持有对方需要的资源,并且都在等待对方释放资源,从而导致程序无法继续执行。
下面是一个简单的死锁示例:
public class DeadlockExample {private static Object resource1 = new Object();private static Object resource2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (resource1) {System.out.println("Thread 1: Holding resource 1...");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1: Waiting for resource 2...");synchronized (resource2) {System.out.println("Thread 1: Holding resource 1 and 2...");}}});Thread thread2 = new Thread(() -> {synchronized (resource2) {System.out.println("Thread 2: Holding resource 2...");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Waiting for resource 1...");synchronized (resource1) {System.out.println("Thread 2: Holding resource 1 and 2...");}}});thread1.start();thread2.start();}
}
在这个例子中,有两个线程分别持有resource1和resource2两个资源,并且都在等待对方释放资源。如果运行这个程序,就会陷入死锁状态,程序无法继续执行。
为了避免死锁问题,我们需要遵循以下原则:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内部占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库事务中执行,否则会出现死锁情况。
三. Java中的并发编程工具类
3.1 synchronized关键字和Lock接口的使用
synchronized关键字
synchronized关键字是Java中最基本的同步机制,可以用于实现线程的同步和互斥。synchronized关键字可以应用于方法和代码块两种形式。
synchronized方法
synchronized方法可以用于保证同一时刻只有一个线程可以访问该方法。当一个线程访问synchronized方法时,其他线程必须等待该线程执行完毕后才能访问该方法。
public synchronized void synchronizedMethod() {// do something
}
synchronized代码块
synchronized代码块可以用于保证同一时刻只有一个线程可以访问该代码块。synchronized代码块需要指定一个锁对象,只有获取该锁对象的线程才能访问该代码块。
Object lock = new Object();
public void synchronizedBlock() {synchronized(lock) {// do something}
}
Lock接口
Lock接口是Java中提供的另一种同步机制,相比于synchronized关键字,Lock接口具有更加灵活的控制能力。Lock接口定义了加锁和释放锁的方法,可以手动控制线程的同步和互斥。
加锁和释放锁
Lock接口定义了两个核心方法:lock()和unlock()。lock()方法用于加锁,只有获取锁的线程才能进入临界区执行代码。unlock()方法用于释放锁,使得其他线程可以获取该锁。
Lock lock = new ReentrantLock();
public void lockMethod() {lock.lock();try {// do something} finally {lock.unlock();}
}
锁的重入
与synchronized关键字不同,Lock接口可以支持锁的重入。当一个线程已经获取了锁,并且在临界区内嵌套了另一个加锁操作时,该线程仍然可以正常执行。
Lock lock = new ReentrantLock();
public void reentrantLock() {lock.lock();try {// do somethinglock.lock();try {// do something} finally {lock.unlock();}} finally {lock.unlock();}
}
3.2 原子变量和原子操作类的使用
原子变量
Java提供了一些原子变量类型,例如AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,可以直接在多线程环境下使用,保证操作的原子性。
下面是AtomicInteger的使用方式:
AtomicInteger count = new AtomicInteger(0);
count.getAndIncrement(); // 原子地增加计数器
需要注意的是,虽然原子变量能够保证操作的原子性,但并不能保证线程安全,因此需要考虑其他的同步机制。
原子操作类
除了原子变量类型之外,Java还提供了一些原子操作类,例如AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray等,可以对数组中的元素进行原子操作。
下面是AtomicIntegerArray的使用方式:
AtomicIntegerArray array = new AtomicIntegerArray(10);
array.getAndIncrement(0); // 原子地对数组元素增加1
需要注意的是,原子操作类的使用方式和原子变量类型类似,同样需要考虑其他的同步机制。
总的来说,原子变量和原子操作类是Java提供的保证操作原子性的机制,可以有效避免竞态条件问题,提高多线程程序的性能和正确性。但需要注意的是,它们并不能完全保证线程安全,仍然需要考虑其他的同步机制。
3.3 Condition接口和ReentrantLock类的使用
ReentrantLock类是一个可重入的互斥锁,它提供了与synchronized关键字类似的功能,但相比synchronized关键字更灵活,能够实现更加复杂的锁定操作。同时,ReentrantLock类还提供了一些高级功能,例如Condition接口,能够实现更加灵活的线程间通信。
ReentrantLock的基本用法
ReentrantLock类的基本用法与synchronized关键字类似,可以用来实现互斥锁,保护共享资源。
下面是ReentrantLock的基本使用方式:
// 定义一个ReentrantLock对象
ReentrantLock lock = new ReentrantLock();// 获取锁
lock.lock();
try {// 访问共享资源
} finally {// 释放锁lock.unlock();
}
需要注意的是,和synchronized关键字一样,ReentrantLock也需要在finally块中释放锁,以确保能够释放锁资源。
Condition接口
Condition接口是ReentrantLock的一个高级功能,能够实现更加灵活的线程间通信。Condition接口可以用来实现等待/通知模式,使得线程能够更加精确地控制等待和唤醒的条件。
下面是Condition接口的基本使用方式:
// 定义一个Condition对象
Condition condition = lock.newCondition();// 等待条件
condition.await();// 唤醒等待条件的线程
condition.signal();
需要注意的是,Condition接口的等待和唤醒操作必须在ReentrantLock的锁保护下进行,否则会抛出IllegalMonitorStateException异常。
ReentrantLock和Condition的综合应用
下面是一个使用ReentrantLock和Condition接口实现生产者消费者模式的示例代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class ProducerConsumerExample {private static final int MAX_CAPACITY = 10;private ReentrantLock lock = new ReentrantLock();private Condition notEmpty = lock.newCondition();private Condition notFull = lock.newCondition();private int count = 0;public void produce() throws InterruptedException {lock.lock();try {while (count == MAX_CAPACITY) {notFull.await();}count++;System.out.println("Produced, count = " + count);notEmpty.signal();} finally {lock.unlock();}}public void consume() throws InterruptedException {lock.lock();try {while (count == 0) {notEmpty.await();}count--;System.out.println("Consumed, count = " + count);notFull.signal();} finally {lock.unlock();}}
}
3.4 CountDownLatch类和CyclicBarrier类的使用
CountDownLatch
CountDownLatch是一个计数器,它的作用是允许一个或多个线程等待一组事件的完成。CountDownLatch有一个计数器,计数器的初始值为一个正整数,每当一个线程完成了一个事件,计数器的值就减一,当计数器的值为0时,表示所有事件都已经完成,此时所有等待该事件的线程就可以继续执行。
使用CountDownLatch的步骤如下:
(1)创建一个CountDownLatch对象,并指定计数器的初始值。
(2)在等待事件的线程中,调用CountDownLatch对象的await()方法进行等待,直到计数器的值变为0。
(3)在完成事件的线程中,完成事件后调用CountDownLatch对象的countDown()方法,计数器的值减一。
示例代码如下:
import java.util.concurrent.CountDownLatch;public class CountDownLatchTest {public static void main(String[] args) {final CountDownLatch latch = new CountDownLatch(3); // 3个事件需要等待new Thread(() -> {try {Thread.sleep(1000);System.out.println("事件1完成");} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown(); // 计数器减一}}).start();new Thread(() -> {try {Thread.sleep(2000);System.out.println("事件2完成");} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown(); // 计数器减一}}).start();new Thread(() -> {try {Thread.sleep(3000);System.out.println("事件3完成");} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown(); // 计数器减一}}).start();try {latch.await(); // 等待所有事件完成System.out.println("所有事件已完成");} catch (InterruptedException e) {e.printStackTrace();}}
}
CyclicBarrier
- 创建CyclicBarrier对象,指定屏障点的数量和达到屏障点时执行的任务。
- 在每个需要等待屏障点的线程中,调用await()方法,等待其他线程到达屏障点。
- 当指定数量的线程都调用了await()方法后,CyclicBarrier会执行指定的任务,然后释放所有等待的线程继续执行。
下面是一个简单的示例,演示如何使用CyclicBarrier:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {public static void main(String[] args) {int n = 3;CyclicBarrier barrier = new CyclicBarrier(n, () -> {System.out.println("所有线程已到达屏障点,开始执行任务!");});for (int i = 0; i < n; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 到达屏障点");barrier.await();System.out.println(Thread.currentThread().getName() + " 继续执行");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}
在上面的示例中,我们创建了一个CyclicBarrier对象,指定了屏障点的数量为3,并且指定了所有线程到达屏障点后要执行的任务。然后我们创建了3个线程,每个线程在到达屏障点后调用了await()方法等待其他线程到达屏障点。当3个线程都到达屏障点后,CyclicBarrier会执行指定的任务,然后释放所有等待的线程继续执行。
3.5 Semaphore类和Exchanger类的使用
Semaphore类
Semaphore类是一个计数信号量,用于控制同时访问某个资源的线程数量。Semaphore维护了一个可配置的许可证数量,线程可以通过acquire()方法获取许可证,release()方法释放许可证。当许可证被全部占用时,后续线程需要等待其他线程释放许可证后才能获取到许可证。
Semaphore类的常用方法:
- acquire():获取一个许可证,如果没有许可证则阻塞等待。
- acquire(int permits):获取指定数量的许可证,如果没有足够的许可证则阻塞等待。
- release():释放一个许可证。
- release(int permits):释放指定数量的许可证。
- availablePermits():获取当前可用的许可证数量。
Semaphore类的使用示例:
import java.util.concurrent.Semaphore;public class SemaphoreDemo {private static final int THREAD_COUNT = 30;private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);private static Semaphore semaphore = new Semaphore(10); // 设置最多允许10个线程同时访问public static void main(String[] args) {for (int i = 0; i < THREAD_COUNT; i++) {threadPool.execute(() -> {try {semaphore.acquire(); // 获取许可证System.out.println(Thread.currentThread().getName() + "获取到许可证,开始执行任务");Thread.sleep(5000); // 模拟任务执行时间System.out.println(Thread.currentThread().getName() + "执行任务完毕,释放许可证");semaphore.release(); // 释放许可证} catch (InterruptedException e) {e.printStackTrace();}});}threadPool.shutdown();}
}
Exchanger类
Exchanger是Java中的一个线程同步工具,它允许两个线程在同一个时刻交换数据。每个线程通过调用exchange()方法来向对方交换数据,当两个线程都调用了该方法后,它们会交换数据并继续执行。
Exchanger的主要方法是exchange()方法,它有两个重载的版本:
public V exchange(V x) throws InterruptedException;public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException;
其中,第一个方法将指定的数据对象与另一个线程交换,如果另一个线程在同一时刻调用了exchange()方法,则它的数据对象也会被返回。如果另一个线程还没有调用exchange()方法,则当前线程会阻塞等待,直到另一个线程调用exchange()方法为止。
第二个方法与第一个方法类似,但是增加了一个超时参数。如果在指定的超时时间内没有另一个线程调用了exchange()方法,则当前线程会抛出TimeoutException异常。
下面是一个简单的示例程序,展示了如何使用Exchanger来交换两个线程之间的数据:
import java.util.concurrent.Exchanger;public class ExchangerDemo {public static void main(String[] args) {Exchanger<String> exchanger = new Exchanger<>();new Thread(() -> {String data1 = "Hello";System.out.println("Thread1: send " + data1);try {String data2 = exchanger.exchange(data1);System.out.println("Thread1: received " + data2);} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(() -> {String data1 = "World";System.out.println("Thread2: send " + data1);try {String data2 = exchanger.exchange(data1);System.out.println("Thread2: received " + data2);} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
四. 并发编程的性能优化和注意事项
- 尽量避免使用锁:锁是并发编程中最常见的同步机制,但它会引入额外的开销,并且容易导致死锁和竞争条件等问题。尽量避免使用锁,可以使用无锁算法、CAS操作、分段锁等替代方案。
- 减少上下文切换:在多线程环境下,线程的切换会引入额外的开销。为了减少上下文切换,可以使用线程池、协程等技术,避免线程的创建和销毁,减少线程之间的切换次数。
- 避免共享变量:共享变量是并发编程中最常见的竞争条件,可能导致数据不一致和线程安全等问题。尽量避免使用共享变量,可以使用线程局部变量、不可变对象等替代方案。
- 合理使用并发容器:Java提供了许多线程安全的容器类,例如ConcurrentHashMap、CopyOnWriteArrayList等。合理使用这些容器类可以避免锁的竞争和死锁等问题。
- 避免死锁:死锁是并发编程中最常见的问题之一,容易导致程序的挂起和性能下降。为了避免死锁,可以使用避免占用多个锁、按照相同的顺序获取锁、设置超时等机制。
- 避免过度设计:并发编程是一种复杂的编程模型,容易产生过度设计和不必要的优化。在编写并发程序时,应该注意避免过度设计,以避免代码的可读性和可维护性降低。
相关文章:
Java中的并发编程模型和常用工具类
本文主要介绍了Java中的并发编程模型和常用工具类,首先阐述了并发编程的概念及其重要性,然后详细介绍了线程的基本概念、生命周期和状态转换、同步与互斥、死锁问题以及线程池的使用和实现原理。接着介绍了synchronized关键字和Lock接口的使用、原子变量…...
第10章 MySQL(一)
10.1 谈谈MySQL的架构 难度:★★ 重点:★ 白话解析 要想彻底的理解MySQL,它的架构一定要先弄清楚,当Java程序员通过JDBC或者Mybatis去执行一条SQL的时候,到底经历了什么。下边先看一幅图: 户端:Java程序员通过JDBC或者Mybatis去拿MySQL的驱动程序,实际上就是拿客户端。…...
英飞凌 Tricore 架构中断系统详解
本文以TC3系列MCU为例,先来了解中断源是如何产生的,再看一下CPU是如何处理中断源的。 AURIX TC3XX的中断路由模块 Interrupt Router (IR) 在TC3中,中断既可以被CPU处理,也可以被DMA处理,所以手册中不再把中断称为中断…...
单例模式:饿汉式
单例模式全局仅一个实例,用于获取公共的内容 头文件mglobalinfomgr.h class MGlobalInfoMgr {MGlobalInfoMgr();~MGlobalInfoMgr(); public:static MGlobalInfoMgr* GetInstance(); private:static MGlobalInfoMgr* _instance; }; 源文件mglobalinfomgr.cpp MGl…...
什么是视图
目录 一、什么是视图 二、视图的作用 三、创建视图 四、使用视图 1.使用视图查询员工信息 五、注意事项 六、补充 一、什么是视图 视图是基于查询的虚拟表,是一个逻辑表,本身并不包含数据。同真实的表一样,视图包含一系列带有名称的列…...
C++——list(2)
作者:几冬雪来 时间:2023年9月28日 内容:C——list内容讲解 目录 前言: list的const迭代器: const的iterator: const迭代器: operator->: 拷贝构造: 迭代器接口补充&…...
Django基础讲解-路由控制器和视图(Django-02)
一 路由控制器 参考链接: Django源码阅读:路由(二) - 知乎 Route路由, 是一种映射关系!路由是把客户端请求的 url路径与视图进行绑定 映射的一种关系。 这个/timer通过路由控制器最终匹配到myapp.views中的视图函数 …...
【算法题】2873. 有序三元组中的最大值 I
题目: 给你一个下标从 0 开始的整数数组 nums 。 请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0 。 下标三元组 (i, j, k) 的值等于 (nums[i]…...
HTML5 跨屏前端框架 Amaze UI
Amaze UI采用国际最前沿的“组件式开发”以及“移动优先”的设计理念,基于其丰富的组件,开发者可通过简单拼装即可快速构建出HTML5网页应用,上线仅半年,Amaze UI就成为了国内最流行的前端框架,目前在Github上收获Star数…...
EXCEL会计记账报表财务软件企业公司做账系统凭证自动生成报表
本系统基于VBA编程设计,具有界面简洁美观,操作方便快捷,功能完备实用的特点,系统分为基本信息、凭证处理、账簿查询、会计报表、固定资产管理、系统管理、凭证数据库七大模块,您只需要录入记账凭证,就可以自…...
Can‘t pickle <class ‘__main__.Test‘>: it‘s not the same object as __main__.Test
目录 原因1 类名重复了 案例1 变量名和类名重复 原因1 类名重复了 检查项目代码,是不是其他地方有同名类。 案例1 变量名和类名重复 转自:python3报错Cant pickle <class __main__.Test>: its not the same object as __main__.Test解决 - 知乎…...
第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和
第五十六天| 第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和 一、1143. 最长公共子序列 题目链接: 题目介绍: 思路: 本题和“最长重复子数组”区别在于**这里不要求是连续的了,但要有相对顺序*…...
腾讯云服务器南京地域详细介绍、测试IP和Ping值测速
腾讯云服务器南京地域怎么样?南京地域很不错,正好处于中间的位置,南方北方用户均可以选择,网络延迟更低速度更快,并且目前南京地域有活动,南京地域可用区可选南京一区、南京二区和南京三区,腾讯…...
理解CSS的层叠性和继承性
CSS的层叠性(cascading)指的是在同一元素上应用多个样式时,不同样式之间的优先级别以及如何进行组合和冲突解决的规则。具体来说,CSS采用的是“选择器优先级”规则来判断哪个样式优先级更高,如果多个样式的优先级相同&…...
OSI体系结构和TCP/IP体系结构
在第一章( 计网第一章 )的时候,曾经提到过OSI体系结构和TCP/IP体系结构,并对它们进行了简单的对比。这篇博客在其基础上进行更深层次的理解。 一.OSI体系结构: 通信子网: 计算机网络在逻辑功能上可以分为…...
侯捷 C++ STL标准库和泛型编程 —— 8 适配器
8 适配器 适配器 Adapter 只是一个小变化,比如改个接口,函数名称等等其出现在三个地方:仿函数适配器,迭代器适配器,容器适配器可以使用继承 / 复合的两种方式实现,STL中都用复合 其思想就是将该记的东西记…...
每日一题 416 分割等和子集(01背包)
题目 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] …...
U盘插上就显示让格式化是坏了吗?
U盘以其体积小巧、存储容量大、读写速度快的特点,在各种工作和个人使用场合中得到了广泛应用,因此深得用户好评。然而,在日常使用U盘的过程中,经常会遇到一些问题和挑战。今天,我将为大家详细解释U盘出现要求格式化的现…...
分布式应用程序协调服务 ZooKeeper 详解
目录 1、ZooKeeper简介 2、ZooKeeper的使用场景 3、ZooKeeper设计目的 4、ZooKeeper数据模型 5、ZooKeeper几个重要概念 5.1、ZooKeeper Session 5.2、ZooKeeper Watch 5.3、Consistency Guarantees 6、ZooKeeper的工作原理 6.1、Leader Election 6.2、Leader工作流…...
Anniversary party(树形dp 基础题)
1.题目大意 There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The University has a hierarchical structure of employees. It means that the supervisor relation forms a tree rooted at the rector V. E. Tretyakov. In …...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
