当前位置: 首页 > news >正文

Java中的并发编程模型和常用工具类

本文主要介绍了Java中的并发编程模型和常用工具类,首先阐述了并发编程的概念及其重要性,然后详细介绍了线程的基本概念、生命周期和状态转换、同步与互斥、死锁问题以及线程池的使用和实现原理。接着介绍了synchronized关键字和Lock接口的使用、原子变量和原子操作类的使用、Condition接口和ReentrantLock类的使用、CountDownLatch类和CyclicBarrier类的使用、Semaphore类和Exchanger类的使用。最后,提出了并发编程的性能优化和注意事项。

一. 引言

1.1 并发编程的概念及重要性

并发编程指的是在多核心、多线程、多任务的操作系统中,同时执行多个任务和线程。在计算机领域中,如今大多数系统都支持并发编程,因为并发编程可以大大提高系统的吞吐量和响应速度,提升系统的性能和可用性。Java作为一门面向对象的编程语言,也提供了一套完善的并发编程模型和工具类库,为Java开发者提供了便捷的并发编程解决方案。

1.2 Java中的并发编程模型和常用工具类简介

Java中的并发编程模型基于线程和锁机制。Java中的线程是轻量级的进程,每个线程都有自己的执行路径,可以独立执行任务。Java中的锁机制可以保证多个线程之间的同步和互斥,避免资源竞争和冲突。Java中的线程和锁机制是Java并发编程的基础。

  1. synchronized关键字和Lock接口的使用 synchronized关键字和Lock接口是Java中的两种锁机制,它们可以保证多个线程之间的同步和互斥。synchronized关键字是Java中最常用的同步机制,它可以用来修饰方法或代码块。Lock接口是Java中提供的另一种同步机制,它可以实现更细粒度的锁定,支持可中断、可重入等高级特性。
  2. 原子变量和原子操作类的使用 Java中的原子变量和原子操作类可以保证数据的原子性,避免多线程之间的竞争和冲突。Java中的原子操作类包括AtomicInteger、AtomicLong、AtomicBoolean等,它们都提供了一系列原子操作方法,例如incrementAndGet、decrementAndGet等,可以实现原子性的数值计算。
  3. Condition接口和ReentrantLock类的使用 Condition接口和ReentrantLock类是Java中用于高级同步机制的工具类。Condition接口是ReentrantLock类的一部分,可以用来实现更高级别的条件等待和通知机制。ReentrantLock类是Java中提供的另一种同步机制,可以实现更细粒度的锁定和高级特性,例如可重入、公平性、可中断等。
  4. CountDownLatch类和CyclicBarrier类的使用 CountDownLatch类和CyclicBarrier类是Java中用于同步多线程的工具类。

二. Java中的并发编程模型

2.1 进程和线程的基本概念

进程和线程是计算机操作系统中的两个基本概念,它们都可以执行任务和程序,但是在实现方式、资源占用、通信方式等方面有所不同。

1. 进程

进程是计算机中执行任务的基本单位,它是操作系统中的一个独立的运行实例。每个进程都有自己的独立地址空间、代码段、数据段、堆栈、文件描述符等系统资源。进程之间相互独立,互不干扰。进程是资源分配的最小单位,它可以分配和使用计算机中的资源,如CPU、内存、文件、网络等。

2. 线程

线程是进程中的一个执行单元,它是操作系统中调度的最小单位。一个进程中可以包含多个线程,这些线程共享进程的地址空间和系统资源,如文件、网络等。线程之间可以通过共享内存的方式进行通信,但是也可能出现竞争和冲突的情况。线程是轻量级的进程,创建和销毁线程的开销相对较小,因此可以更加高效地利用计算机的资源。

3. 进程和线程的区别

进程和线程的主要区别在于资源占用、调度和通信方式等方面。进程是资源分配的最小单位,它可以分配和使用计算机中的资源,但是进程之间的通信需要通过IPC(Inter-Process Communication)机制,开销相对较大。线程是轻量级的进程,它共享进程的地址空间和系统资源,因此线程之间的通信和调度开销相对较小。但是线程之间可能出现竞争和冲突的情况,需要进行同步和互斥操作。

2.2 线程的生命周期和状态转换

线程的生命周期包括五种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。这些状态可以通过不同的方法进行转换,下面是每个状态的含义、转换条件:

  1. 新建状态(New) 线程被创建但是还没有开始运行的状态,此时线程并没有分配到CPU资源,只是一个空壳子。新建状态的线程可以通过start()方法启动。
  2. 就绪状态(Runnable) 线程被分配到CPU资源并且可以运行的状态,但是并不一定正在执行,等待CPU调度。就绪状态的线程可以通过线程调度器进行调度,进入运行状态。
  3. 运行状态(Running) 线程正在执行的状态,正在占用CPU资源,执行任务。运行状态的线程可以通过sleep()、yield()、wait()等方法进入阻塞状态,也可以通过线程调度器的调度进入就绪状态。
  4. 阻塞状态(Blocked) 线程因为某些原因无法执行任务,暂时放弃CPU资源的状态。阻塞状态包括多种类型:等待阻塞(wait)、同步阻塞(synchronized)、其他阻塞(sleep、join、park等)。当阻塞状态结束后,线程可以重新进入就绪状态等待CPU调度。
  5. 死亡状态(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两个资源,并且都在等待对方释放资源。如果运行这个程序,就会陷入死锁状态,程序无法继续执行。

为了避免死锁问题,我们需要遵循以下原则:

  1. 避免一个线程同时获取多个锁。
  2. 避免一个线程在锁内部占用多个资源,尽量保证每个锁只占用一个资源。
  3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
  4. 对于数据库锁,加锁和解锁必须在一个数据库事务中执行,否则会出现死锁情况。

三. 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
  1. 创建CyclicBarrier对象,指定屏障点的数量和达到屏障点时执行的任务。
  2. 在每个需要等待屏障点的线程中,调用await()方法,等待其他线程到达屏障点。
  3. 当指定数量的线程都调用了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();}
}

四. 并发编程的性能优化和注意事项

  1. 尽量避免使用锁:锁是并发编程中最常见的同步机制,但它会引入额外的开销,并且容易导致死锁和竞争条件等问题。尽量避免使用锁,可以使用无锁算法、CAS操作、分段锁等替代方案。
  2. 减少上下文切换:在多线程环境下,线程的切换会引入额外的开销。为了减少上下文切换,可以使用线程池、协程等技术,避免线程的创建和销毁,减少线程之间的切换次数。
  3. 避免共享变量:共享变量是并发编程中最常见的竞争条件,可能导致数据不一致和线程安全等问题。尽量避免使用共享变量,可以使用线程局部变量、不可变对象等替代方案。
  4. 合理使用并发容器:Java提供了许多线程安全的容器类,例如ConcurrentHashMap、CopyOnWriteArrayList等。合理使用这些容器类可以避免锁的竞争和死锁等问题。
  5. 避免死锁:死锁是并发编程中最常见的问题之一,容易导致程序的挂起和性能下降。为了避免死锁,可以使用避免占用多个锁、按照相同的顺序获取锁、设置超时等机制。
  6. 避免过度设计:并发编程是一种复杂的编程模型,容易产生过度设计和不必要的优化。在编写并发程序时,应该注意避免过度设计,以避免代码的可读性和可维护性降低。

相关文章:

Java中的并发编程模型和常用工具类

本文主要介绍了Java中的并发编程模型和常用工具类&#xff0c;首先阐述了并发编程的概念及其重要性&#xff0c;然后详细介绍了线程的基本概念、生命周期和状态转换、同步与互斥、死锁问题以及线程池的使用和实现原理。接着介绍了synchronized关键字和Lock接口的使用、原子变量…...

第10章 MySQL(一)

10.1 谈谈MySQL的架构 难度:★★ 重点:★ 白话解析 要想彻底的理解MySQL,它的架构一定要先弄清楚,当Java程序员通过JDBC或者Mybatis去执行一条SQL的时候,到底经历了什么。下边先看一幅图: 户端:Java程序员通过JDBC或者Mybatis去拿MySQL的驱动程序,实际上就是拿客户端。…...

英飞凌 Tricore 架构中断系统详解

本文以TC3系列MCU为例&#xff0c;先来了解中断源是如何产生的&#xff0c;再看一下CPU是如何处理中断源的。 AURIX TC3XX的中断路由模块 Interrupt Router (IR) 在TC3中&#xff0c;中断既可以被CPU处理&#xff0c;也可以被DMA处理&#xff0c;所以手册中不再把中断称为中断…...

单例模式:饿汉式

单例模式全局仅一个实例&#xff0c;用于获取公共的内容 头文件mglobalinfomgr.h class MGlobalInfoMgr {MGlobalInfoMgr();~MGlobalInfoMgr(); public:static MGlobalInfoMgr* GetInstance(); private:static MGlobalInfoMgr* _instance; }; 源文件mglobalinfomgr.cpp MGl…...

什么是视图

目录 一、什么是视图 二、视图的作用 三、创建视图 四、使用视图 1.使用视图查询员工信息 五、注意事项 六、补充 一、什么是视图 视图是基于查询的虚拟表&#xff0c;是一个逻辑表&#xff0c;本身并不包含数据。同真实的表一样&#xff0c;视图包含一系列带有名称的列…...

C++——list(2)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年9月28日 内容&#xff1a;C——list内容讲解 目录 前言&#xff1a; list的const迭代器&#xff1a; const的iterator&#xff1a; const迭代器&#xff1a; operator->: 拷贝构造&#xff1a; 迭代器接口补充&…...

Django基础讲解-路由控制器和视图(Django-02)

一 路由控制器 参考链接&#xff1a; Django源码阅读&#xff1a;路由&#xff08;二&#xff09; - 知乎 Route路由, 是一种映射关系&#xff01;路由是把客户端请求的 url路径与视图进行绑定 映射的一种关系。 这个/timer通过路由控制器最终匹配到myapp.views中的视图函数 …...

【算法题】2873. 有序三元组中的最大值 I

题目&#xff1a; 给你一个下标从 0 开始的整数数组 nums 。 请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中&#xff0c;找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数&#xff0c;则返回 0 。 下标三元组 (i, j, k) 的值等于 (nums[i]…...

HTML5 跨屏前端框架 Amaze UI

Amaze UI采用国际最前沿的“组件式开发”以及“移动优先”的设计理念&#xff0c;基于其丰富的组件&#xff0c;开发者可通过简单拼装即可快速构建出HTML5网页应用&#xff0c;上线仅半年&#xff0c;Amaze UI就成为了国内最流行的前端框架&#xff0c;目前在Github上收获Star数…...

EXCEL会计记账报表财务软件企业公司做账系统凭证自动生成报表

本系统基于VBA编程设计&#xff0c;具有界面简洁美观&#xff0c;操作方便快捷&#xff0c;功能完备实用的特点&#xff0c;系统分为基本信息、凭证处理、账簿查询、会计报表、固定资产管理、系统管理、凭证数据库七大模块&#xff0c;您只需要录入记账凭证&#xff0c;就可以自…...

Can‘t pickle <class ‘__main__.Test‘>: it‘s not the same object as __main__.Test

目录 原因1 类名重复了 案例1 变量名和类名重复 原因1 类名重复了 检查项目代码&#xff0c;是不是其他地方有同名类。 案例1 变量名和类名重复 转自&#xff1a;python3报错Cant pickle <class __main__.Test>: its not the same object as __main__.Test解决 - 知乎…...

第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和

第五十六天| 第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和 一、1143. 最长公共子序列 题目链接&#xff1a; 题目介绍&#xff1a; 思路&#xff1a; 本题和“最长重复子数组”区别在于**这里不要求是连续的了&#xff0c;但要有相对顺序*…...

腾讯云服务器南京地域详细介绍、测试IP和Ping值测速

腾讯云服务器南京地域怎么样&#xff1f;南京地域很不错&#xff0c;正好处于中间的位置&#xff0c;南方北方用户均可以选择&#xff0c;网络延迟更低速度更快&#xff0c;并且目前南京地域有活动&#xff0c;南京地域可用区可选南京一区、南京二区和南京三区&#xff0c;腾讯…...

理解CSS的层叠性和继承性

CSS的层叠性&#xff08;cascading&#xff09;指的是在同一元素上应用多个样式时&#xff0c;不同样式之间的优先级别以及如何进行组合和冲突解决的规则。具体来说&#xff0c;CSS采用的是“选择器优先级”规则来判断哪个样式优先级更高&#xff0c;如果多个样式的优先级相同&…...

OSI体系结构和TCP/IP体系结构

在第一章&#xff08; 计网第一章 &#xff09;的时候&#xff0c;曾经提到过OSI体系结构和TCP/IP体系结构&#xff0c;并对它们进行了简单的对比。这篇博客在其基础上进行更深层次的理解。 一.OSI体系结构&#xff1a; 通信子网&#xff1a; 计算机网络在逻辑功能上可以分为…...

侯捷 C++ STL标准库和泛型编程 —— 8 适配器

8 适配器 适配器 Adapter 只是一个小变化&#xff0c;比如改个接口&#xff0c;函数名称等等其出现在三个地方&#xff1a;仿函数适配器&#xff0c;迭代器适配器&#xff0c;容器适配器可以使用继承 / 复合的两种方式实现&#xff0c;STL中都用复合 其思想就是将该记的东西记…...

每日一题 416 分割等和子集(01背包)

题目 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 示例 1&#xff1a; 输入&#xff1a;nums [1,5,11,5] 输出&#xff1a;true 解释&#xff1a;数组可以分割成 [1, 5, 5] …...

U盘插上就显示让格式化是坏了吗?

U盘以其体积小巧、存储容量大、读写速度快的特点&#xff0c;在各种工作和个人使用场合中得到了广泛应用&#xff0c;因此深得用户好评。然而&#xff0c;在日常使用U盘的过程中&#xff0c;经常会遇到一些问题和挑战。今天&#xff0c;我将为大家详细解释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 …...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...