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

Java大师成长计划之第17天:锁与原子操作

📢 友情提示:

本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。

在多线程编程中,如何保证线程安全是开发者必须面对的重要课题。为了保证共享资源的正确访问,Java提供了多种锁机制和原子操作工具。在本篇博客中,我们将详细探讨sychronized关键字、java.util.concurrent包中的并发工具类,以及如何使用这些工具来确保线程安全。

一. synchronized关键字

1.1 什么是synchronized

synchronized是Java中一种用于线程同步的关键字,它保证了在同一时刻只有一个线程可以执行被synchronized修饰的代码块或方法,从而确保对共享资源的线程安全访问。synchronized关键字使得线程可以排队执行同步区域中的代码,防止多个线程在同一时间访问共享资源时发生数据冲突或不一致的问题。

synchronized是实现线程安全最简单、最基础的工具之一。在多线程环境下,synchronized通过加锁的方式确保对共享资源的互斥访问,它可以被应用于方法或代码块。

1.2 synchronized的使用方式

Java中的synchronized关键字可以修饰方法或代码块。两种使用方式的区别在于锁的范围不同:当修饰方法时,整个方法都被加锁;当修饰代码块时,只有代码块内的部分被加锁。

1.2.1 同步方法

通过在方法声明中添加synchronized关键字,表示该方法是同步的。当线程调用该方法时,它必须首先获取该对象的锁,其他线程将被阻塞,直到当前线程执行完毕并释放锁。

public class Counter {private int count = 0;// 同步方法public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}

在这个例子中,increment()getCount()方法都是同步的。当多个线程同时调用这两个方法时,synchronized保证了在同一时刻只有一个线程能够进入这两个方法,这样可以避免多个线程同时修改count导致的竞态条件。

1.2.1.1 锁的对象

synchronized修饰实例方法时,它锁定的是当前对象的监视器(monitor)。这意味着对于同一个对象的多个线程调用,只有一个线程能够访问该同步方法,其他线程会被阻塞直到锁被释放。

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

在上面的例子中,increment()方法被修饰为同步方法,锁的是当前对象的监视器。每当一个线程执行该方法时,它会获取当前对象的锁,从而保证只有一个线程能够执行该方法。

1.2.1.2 静态方法中的synchronized

synchronized修饰静态方法时,它锁定的是Class对象的监视器,而不是当前实例对象的监视器。也就是说,同一个类的所有线程访问静态方法时,都会争夺类对象的锁。

public static synchronized void incrementStatic() {count++;
}

在这种情况下,无论创建多少个类的实例,所有线程调用incrementStatic()方法时,都只能同时有一个线程在执行,因为它们争夺的是类锁。

1.2.2 同步代码块

synchronized还可以应用于代码块内,用来限制加锁的范围。与同步方法不同,同步代码块可以让开发者更精细地控制锁的粒度,只对需要同步的部分进行加锁,其他部分可以并行执行,从而提高程序的性能。

public class Counter {private int count = 0;public void increment() {synchronized (this) { // 锁定当前对象count++;}}public int getCount() {return count;}
}

在上面的代码中,synchronized (this)表示在increment()方法内部,只对count++操作进行加锁。这样,其他非同步代码部分可以并发执行,不会受到锁的影响,从而提高效率。

1.2.2.1 锁定任意对象

sychronized关键字中的对象锁定,不必局限于this,可以锁定任何对象。例如,使用类对象作为锁:

public class Counter {private int count = 0;private static final Object lock = new Object(); // 锁对象public void increment() {synchronized (lock) { // 锁定指定对象count++;}}public int getCount() {return count;}
}

在此例中,increment()方法内的synchronized块锁定了一个名为lock的静态对象。通过控制锁的对象,开发者可以实现更细粒度的同步控制,以避免不必要的资源竞争。

1.3 synchronized的原理

1.3.1 内部机制

Java中的synchronized通过锁机制来确保线程安全。具体而言,每个对象都有一个监视器锁(monitor),当线程执行synchronized代码块时,线程会首先尝试获取对象的锁。如果当前线程成功获得锁,其他线程将被阻塞,直到当前线程执行完毕并释放锁。释放锁时,其他被阻塞的线程将有机会争夺锁。

  • 锁的获取:当线程执行被synchronized修饰的代码时,它首先要获取对象的监视器(锁)。如果其他线程已经持有该锁,当前线程就会进入阻塞状态,直到锁被释放。
  • 锁的释放:当线程执行完同步方法或代码块后,自动释放锁,其他线程可以获取锁并继续执行。
1.3.2 锁的竞争与阻塞

当多个线程同时访问同一个同步方法或同步代码块时,它们会争夺锁。此时,其他线程无法继续执行被同步的代码,必须等待锁的释放。这种机制是实现线程同步的基础,但是也带来了性能上的开销,因为线程在等待锁时需要消耗CPU资源。

例如,如果有多个线程同时请求执行synchronized修饰的方法,只有一个线程能够成功获得锁并执行该方法,其他线程将被阻塞,直到获得锁为止。这会导致线程的上下文切换和阻塞等待,从而影响程序的执行效率。

1.3.3 死锁

死锁是多线程编程中的一个经典问题,通常发生在多个线程相互等待对方释放资源时。具体来说,死锁是指两个或更多线程在执行过程中因争夺资源而造成的相互等待,导致程序无法继续执行。

public class DeadlockExample {private final Object lock1 = new Object();private final Object lock2 = new Object();public void method1() {synchronized (lock1) {synchronized (lock2) {System.out.println("Method 1 executed");}}}public void method2() {synchronized (lock2) {synchronized (lock1) {System.out.println("Method 2 executed");}}}
}

在这个例子中,method1()method2()两个方法分别锁住lock1lock2。如果线程A先执行method1()并获得lock1,然后执行method2()并等待lock2,同时线程B执行method2()并获得lock2,然后尝试获取lock1,就会发生死锁。此时,两个线程相互等待对方释放锁,导致程序无法继续执行。

1.3.4 死锁的预防

为了避免死锁的发生,可以遵循以下几个策略:

  • 避免锁嵌套:尽量避免在一个同步方法中再次获取其他锁。
  • 锁的顺序:保证所有线程获取锁的顺序一致,从而避免互相等待。
  • 使用tryLock()ReentrantLock提供的tryLock()方法可以在指定时间内尝试获取锁,如果无法获取锁,当前线程可以选择其他操作,从而避免长时间等待。

1.4 synchronized的性能影响

synchronized关键字虽然能够保证线程安全,但也带来了一定的性能开销。主要的原因是锁的获取和释放操作是相对耗时的,尤其在高并发环境下,线程在等待锁时会产生上下文切换,从而增加系统负担。

1.4.1 锁竞争

当多个线程竞争同一锁时,线程的阻塞和唤醒会导致较高的上下文切换开销。如果竞争非常激烈,锁的持有时间较长,可能会导致系统性能下降。

1.4.2 锁的粒度

锁的粒度越大,性能开销就越大。为避免不必要的性能损失,可以尽量缩小同步的代码块范围,只对关键操作进行同步,而不是同步整个方法。

1.5 小结

synchronized是Java中最常用的线程同步机制,它通过锁的方式确保了在多线程环境中共享资源的安全访问。虽然synchronized非常简洁且容易使用,但它也有一定的性能开销,因此在高并发的场景下,开发者需要谨慎使用,并考虑使用更高效的同步机制(如ReentrantLockReadWriteLock等)。此外,死锁是使用synchronized时必须特别注意的问题,避免死锁的发生可以提高程序的可靠性和性能。

掌握sychronized关键字的正确使用,对于开发高效的并发程序是至关重要的。

二. java.util.concurrent包中的工具

Java提供了java.util.concurrent包中的许多工具类,以帮助开发者更加高效地实现线程安全、并发控制和任务调度。相较于传统的synchronized关键字,java.util.concurrent包中的工具类提供了更细粒度的控制,更高效的并发性,并且具备更灵活的配置选项。本文将重点介绍其中一些重要的并发工具,包括Executor框架、ReentrantLockSemaphoreCountDownLatchCyclicBarrierBlockingQueue等工具类。

2.1 Executor框架

Executor框架是java.util.concurrent包中最重要的组件之一,它负责管理线程池及任务调度。与传统的手动创建线程相比,Executor框架可以有效减少线程创建和销毁的开销,提高任务执行效率。

2.1.1 Executor接口

Executor接口是一个简单的接口,定义了一个方法void execute(Runnable command),用于提交一个Runnable任务。通过该接口,开发者可以通过线程池来提交任务,而不必直接创建和管理线程。

2.1.2 ExecutorService接口

ExecutorService继承了Executor接口,增加了更多与任务生命周期相关的方法。ExecutorService接口允许开发者提交Callable任务,并返回一个Future对象,可以获取任务执行的结果。

常见的ExecutorService实现类有:

  • ThreadPoolExecutor:最常用的线程池实现类,支持动态调整线程池的大小。
  • ScheduledThreadPoolExecutor:支持定时和周期性任务的线程池。
  • Executors:一个工厂类,提供创建常用线程池的静态方法,如固定大小线程池、单线程池、可缓存线程池等。
2.1.2.1 线程池的使用
import java.util.concurrent.*;public class ExecutorServiceExample {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交一个任务executor.submit(() -> {System.out.println("Task is being executed by thread: " + Thread.currentThread().getName());});// 关闭线程池executor.shutdown();}
}

在这个例子中,我们通过Executors.newFixedThreadPool(3)创建了一个包含三个线程的线程池。通过调用submit()方法,我们将一个任务提交给线程池执行。

2.2 ReentrantLock

ReentrantLockjava.util.concurrent.locks包中的一个重要类,它实现了Lock接口,提供了比synchronized更加灵活的锁机制。通过ReentrantLock,开发者可以显式地控制锁的获取和释放,避免了synchronized带来的潜在问题,如死锁。

2.2.1 基本用法
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private final Lock lock = new ReentrantLock();public void increment() {lock.lock(); // 获取锁try {count++;} finally {lock.unlock(); // 确保释放锁}}public int getCount() {return count;}
}

在这个例子中,我们使用ReentrantLock代替synchronized来保证线程安全。通过显式地调用lock.lock()lock.unlock(),我们能更灵活地控制锁的使用,避免潜在的死锁问题。

2.2.2 可重入性

ReentrantLock的一个特点是它是可重入的,这意味着同一线程可以多次获取同一个锁,而不会发生死锁。

Lock lock = new ReentrantLock();
lock.lock(); // 第一次获取锁
lock.lock(); // 第二次获取锁
// 执行操作
lock.unlock(); // 第一次释放锁
lock.unlock(); // 第二次释放锁

2.3 Semaphore

Semaphore是一个用来控制同时访问某个特定资源的线程数量的类。它通过维护一个计数器来控制最大并发线程数,常用于限制资源的并发访问。例如,数据库连接池、限流器等场景。

2.3.1 基本用法
import java.util.concurrent.*;public class SemaphoreExample {private static final Semaphore semaphore = new Semaphore(3); // 最多允许3个线程访问public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(() -> {try {semaphore.acquire(); // 请求许可System.out.println(Thread.currentThread().getName() + " is accessing the resource");Thread.sleep(1000); // 模拟访问资源} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); // 释放许可System.out.println(Thread.currentThread().getName() + " released the resource");}}).start();}}
}

在这个例子中,Semaphore被设置为最多允许3个线程同时访问资源。每个线程在访问资源时通过acquire()方法请求许可,访问完成后通过release()方法释放许可。这样,最多只有3个线程可以同时访问共享资源。

2.4 CountDownLatch

CountDownLatch是一个同步辅助工具,它允许一个或多个线程一直等待,直到其他线程完成一组操作后再继续执行。CountDownLatch通常用于等待多个线程完成某些任务后,再执行后续的操作。

2.4.1 基本用法
import java.util.concurrent.*;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int taskCount = 3;CountDownLatch latch = new CountDownLatch(taskCount); // 初始化CountDownLatchfor (int i = 0; i < taskCount; i++) {final int taskId = i + 1;new Thread(() -> {try {Thread.sleep(1000); // 模拟任务System.out.println("Task " + taskId + " completed");latch.countDown(); // 每完成一个任务,调用countDown()方法} catch (InterruptedException e) {e.printStackTrace();}}).start();}latch.await(); // 主线程在这里等待,直到countDown()的调用次数为0System.out.println("All tasks are completed. Main thread can proceed.");}
}

在这个示例中,CountDownLatch用于等待3个子线程完成任务。主线程调用latch.await()阻塞,直到所有子线程完成任务并调用countDown()。一旦计数器达到0,主线程继续执行。

2.5 CyclicBarrier

CyclicBarrier是一个同步辅助工具,允许一组线程互相等待,直到所有线程都到达某个公共屏障点。CyclicBarrier可以用来实现多线程并行计算的场景,在所有线程都完成某个任务之后再开始下一阶段的工作。

2.5.1 基本用法
import java.util.concurrent.*;public class CyclicBarrierExample {public static void main(String[] args) throws InterruptedException {int threadCount = 3;CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {System.out.println("All threads have reached the barrier, resuming...");});for (int i = 0; i < threadCount; i++) {final int taskId = i + 1;new Thread(() -> {try {Thread.sleep(1000); // 模拟工作System.out.println("Thread " + taskId + " is ready");barrier.await(); // 等待其他线程到达屏障} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}

在这个例子中,CyclicBarrier创建了一个屏障,所有线程都需要在barrier.await()处等待,直到所有线程都到达屏障点,之后才会继续执行。CyclicBarrier的一个特点是可以重复使用,所以它是“可循环”的。

2.6 BlockingQueue

BlockingQueuejava.util.concurrent包中的一个接口,它用于解决生产者-消费者问题。BlockingQueue提供了线程安全的插入、删除和访问操作,当队列满时,生产者会被阻塞,直到有空间;当队列空时,消费者会被阻塞,直到有数据。

常用的BlockingQueue实现类有:

  • ArrayBlockingQueue:基于数组的有界队列。
  • LinkedBlockingQueue:基于链表的可选有界或无界队列。
  • PriorityBlockingQueue:支持优先级的无界队列。
2.6.1 基本用法
import java.util.concurrent.*;public class BlockingQueueExample {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);// 生产者线程new Thread(() -> {try {for (int i = 0; i < 10; i++) {queue.put(i); // 如果队列满,阻塞等待System.out.println("Produced: " + i);}} catch (InterruptedException e) {e.printStackTrace();}}).start();// 消费者线程new Thread(() -> {try {for (int i = 0; i < 10; i++) {Integer item = queue.take(); // 如果队列空,阻塞等待System.out.println("Consumed: " + item);}} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

在这个例子中,BlockingQueue用于实现生产者-消费者模型。生产者线程通过queue.put()方法将数据放入队列,消费者线程通过queue.take()方法从队列中取出数据。BlockingQueue提供了自动阻塞的机制,避免了竞争和数据丢失。

2.7 总结

java.util.concurrent包提供了许多强大的工具类,帮助开发者高效地处理并发编程中的各种问题。从线程池管理(Executor)到锁机制(ReentrantLockSemaphore),从同步辅助工具(CountDownLatchCyclicBarrier)到队列(BlockingQueue),这些工具大大简化了并发编程的复杂性,提高了程序的性能和可扩展性。

掌握这些工具类的使用,可以帮助你在并发编程中更好地控制资源、避免死锁、提高程序的并发性和响应性。

三. 死锁与锁优化

在并发编程中,死锁是一个常见且危险的问题,它可能导致程序完全停止工作。而锁优化则是为了提高程序并发性能,避免因锁竞争带来的性能瓶颈。理解死锁的成因、如何避免死锁以及如何优化锁的使用,能够帮助开发者编写更加高效且健壮的并发代码。

3.1 死锁的概念

死锁(Deadlock)是指多个线程在执行过程中,由于竞争资源并且形成循环等待,从而导致线程无法继续执行的状态。死锁会使得程序中的某些线程永远处于阻塞状态,进而使得整个系统的功能受到影响,甚至完全停滞。

3.1.1 死锁的四个必要条件

死锁的发生需要满足以下四个条件:

  1. 互斥条件:至少有一个资源是以排他方式分配的,即某一时刻,只有一个线程能够使用该资源。
  2. 占有并等待:至少有一个线程持有一个资源,并等待获取其他线程持有的资源。
  3. 不剥夺:已经分配给线程的资源,在没有使用完之前,不能被其他线程强制剥夺。
  4. 循环等待:存在一个线程等待资源的循环,换句话说,线程1等待线程2持有的资源,线程2等待线程3持有的资源,直到线程N等待线程1持有的资源,形成闭环。
3.1.2 死锁的示例

以下是一个经典的死锁示例,两个线程通过不同的顺序获取两个资源,导致互相等待,最终发生死锁。

public class DeadlockExample {private final Object lock1 = new Object();private final Object lock2 = new Object();public void method1() {synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " locked lock1");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " locked lock2");}}}public void method2() {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " locked lock2");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " locked lock1");}}}public static void main(String[] args) {DeadlockExample deadlock = new DeadlockExample();Thread t1 = new Thread(deadlock::method1);Thread t2 = new Thread(deadlock::method2);t1.start();t2.start();}
}

在这个例子中,method1()method2()分别锁定lock1lock2,并且在等待对方持有的锁时发生死锁。当Thread-1在执行method1()时锁定了lock1并尝试获取lock2,而Thread-2则在执行method2()时锁定了lock2并尝试获取lock1。这导致两个线程相互等待,无法继续执行。

3.2 死锁的预防与解决

3.2.1 死锁的预防策略

为了避免死锁的发生,可以采取以下几种预防措施:

  1. 避免锁嵌套:尽量避免在一个锁内再次请求其他锁。避免多个锁的嵌套调用是减少死锁风险的有效手段。

  2. 获取锁的顺序一致:如果多个线程需要获取多个锁,保证所有线程按照相同的顺序来请求锁。这样就避免了因不同线程以不同顺序获取锁而造成的死锁。

    // 保证所有线程都按相同顺序请求锁
    synchronized (lock1) {synchronized (lock2) {// 处理任务}
    }
    
  3. 使用tryLock()避免阻塞ReentrantLock提供的tryLock()方法允许线程在请求锁时指定等待时间,如果在规定时间内未能获取锁,线程会放弃尝试而不再阻塞。通过这种方式,可以避免长时间的资源等待,减少死锁发生的概率。

    Lock lock1 = new ReentrantLock();
    Lock lock2 = new ReentrantLock();if (lock1.tryLock() && lock2.tryLock()) {try {// 执行任务} finally {lock1.unlock();lock2.unlock();}
    } else {// 无法获得锁,处理失败逻辑
    }
    
3.2.2 死锁的检测与恢复

在复杂的并发应用中,死锁有时是难以完全避免的,特别是当涉及到多个资源和复杂的线程交互时。为了解决死锁问题,可以采取以下方法:

  1. 检测死锁:Java的ThreadMXBean可以用来检测死锁。通过调用ThreadMXBean.findDeadlockedThreads()方法,我们可以检测系统中的死锁情况并采取相应的措施。

    import java.lang.management.*;public class DeadlockDetection {public static void main(String[] args) {ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();if (deadlockedThreads != null) {System.out.println("Deadlock detected!");// 处理死锁:例如打印死锁线程信息并终止线程}}
    }
    
  2. 恢复策略:当死锁发生时,可以采用如下策略:

    • 终止线程:强制中止某些线程,释放其持有的锁,打破死锁。
    • 重试机制:如果检测到死锁,程序可以重试请求锁的过程,或者根据设定的超时机制放弃。

3.3 锁优化

3.3.1 锁粒度与锁竞争

锁的粒度影响着并发程序的性能。如果锁的粒度过大,比如将整个方法加锁,可能会导致不必要的线程等待。而如果锁的粒度过小,可能会增加锁竞争的概率,导致性能问题。为了优化锁的使用,应该根据任务的性质合理地选择锁的粒度。

  1. 细化锁粒度:尽量将锁的范围限制在关键代码段,以减少锁的持有时间。

    // 锁粒度过大,整个方法都被同步
    public synchronized void processData() {// 执行处理
    }// 锁粒度适当缩小,只同步必要的代码
    public void processData() {synchronized (lock) {// 执行关键操作}
    }
    
  2. 减少锁竞争:减少不同线程同时竞争同一资源的概率。例如,可以使用读写锁(ReadWriteLock),允许多个线程并行读取共享资源,只有在写操作时才会互斥。

3.3.2 使用ReentrantLock进行锁优化

ReentrantLock提供了比synchronized更灵活的锁控制,它可以显式地尝试获取锁,控制锁的获取时间,从而避免死锁和锁竞争问题。

  • 公平锁ReentrantLock支持公平锁机制,即按照线程请求锁的顺序来获取锁,这可以减少资源的争抢。

    Lock lock = new ReentrantLock(true); // 公平锁
    
  • 可重入性ReentrantLock是可重入的,即同一线程可以多次获取同一把锁。可以通过lock.lock()lock.unlock()明确控制锁的获取和释放。

  • 中断响应ReentrantLock支持响应中断,允许线程在等待锁的过程中被中断,这对于避免长时间等待产生的死锁具有重要意义。

    Lock lock = new ReentrantLock();
    try {if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {// 执行任务} else {// 处理无法获得锁的情况}
    } catch (InterruptedException e) {// 响应中断
    }
    
3.3.3 读写锁优化

在读多写少的场景中,可以使用ReadWriteLock,这能显著提高并发性能。ReadWriteLock通过将读锁和写锁分开管理,允许多个线程并行读取,而写线程则需要独占访问资源,从而提高了系统的吞吐量。

ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
try {// 执行读取操作
} finally {lock.readLock().unlock();
}lock.writeLock().lock();
try {// 执行写操作
} finally {lock.writeLock().unlock();
}

3.4 小结

死锁是多线程编程中的常见问题,其发生通常是由于资源的竞争和线程之间不当的锁管理引起的。通过合理设计锁的获取顺序、避免锁嵌套、使用tryLock()等方法,可以有效减少死锁的发生。而在优化锁的使用时,合理选择锁粒度、使用ReentrantLockReadWriteLock等工具类,可以大幅提高并发程序的性能。

开发者需要时刻关注死锁的可能性,并在开发过程中采取相应的预防措施。此外,合理使用锁优化技术,将帮助我们在保证线程安全的同时,提高系统的并发能力和性能。

四. 总结

在Java中,锁和原子操作是保证多线程并发安全的重要工具。从基础的synchronized到更灵活的ReentrantLockReadWriteLock,以及高效的Atomic类,它们各自有不同的使用场景和特点。通过合理使用这些工具,开发者可以保证线程安全并提高程序的性能。

在实际开发中,我们不仅要关注锁的正确使用,还需要避免死锁,合理优化锁的粒度和范围,确保并发程序既安全又高效。掌握这些并发工具,将有助于你在多线程编程的道路上走得更远。

相关文章:

Java大师成长计划之第17天:锁与原子操作

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4o-mini模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 在多线程编程中&#xff0c;如何保证…...

Tailwind CSS v4 主题化实践入门(自定义 Theme + 主题模式切换)✨

ok&#xff0c;经过学习Tailwindcss我决定将此专栏建设成为一个Tailwindcss实战专栏&#xff0c;我将在专栏内完成5050挑战&#xff1a;50天50个Tailwindcss练习项目&#xff0c;欢迎大家订阅&#xff01;&#xff01;&#xff01; Tailwind CSS v4 带来了更强大的主题定制能力…...

[C++] 大数减/除法

目录 高精度博客 - 前两讲高精度减法高精度除法高精度系列函数完整版 高精度博客 - 前两讲 讲次名称链接高精加法[C] 高精度加法(作用 模板 例题)高精乘法[C] 高精度乘法 高精度减法 void subBIG(int x[], int y[], int z[]){z[0] max(x[0], y[0]);for(int i 1; i < …...

数据链共享:从印巴空战到工业控制的跨越性应用

摘要 本文通过对印巴空战中数据链共享发挥关键作用的分析&#xff0c;引出数据链共享在工业控制领域同样具有重大价值的观点。深入阐述 DIOS 工业控制操作系统作为工业数据链共享基础技术的特点、架构及应用优势&#xff0c;对比空战场景与工业控制场景下数据链共享的相…...

加速pip下载:永久解决网络慢问题

一文教你解决 pip 下载太慢了的问题 || 下载时因为网络不好中断下载的问题 一、找到 pip 配置文件路径 1.配置文件位置&#xff1a; Windows 系统的 pip 配置文件默认不存在&#xff0c;需要手动创建&#xff0c;路径为&#xff1a; C:\Users\你的用户名\pip\pip.ini 用户目…...

无线网络设备中AP和AC是什么?有什么区别?

无线网络设备中AP和AC是什么&#xff1f;有什么区别&#xff1f; 一. 什么是AP&#xff1f;二. 什么是AC&#xff1f;三. AP与AC的关系 前言 肝文不易&#xff0c;点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都…...

软考中级数据库备考-上午篇

背景 新工作主要做大数据平台&#xff0c;考一个软考中级数据库系统工程师&#xff0c;补足一下基础知识。 基础知识 1.计算机硬件基础知识 正确答案:C 正确答案:D 正确答案:C 正确答案&#xff1a;BC 正确答案&#xff1a;B 正确答案:D 正确答案:A DMA建立内存与外设的直接…...

opencv处理图像(二)

接下来进入到程序线程设计部分 我们主线程负责图形渲染等操作&#xff0c;OpenGL的限制&#xff0c;opencv技术对传入图像加以处理&#xff0c;输出预期图像给主线程 QThread 我之前也是在想给opencv开一个专门的线程&#xff0c;但经过了解有几个弊端&#xff0c;第一资源浪…...

powerbuilder9.0中文版

经常 用这个版本号写小软件,非常喜欢这个开发软件 . powerbuilder9.0 非常的小巧,快捷,功能强大,使用方便. 我今天用软件 自己汉化了一遍&#xff0c;一些常用的界面都已经翻译成中文。 我自己用的&#xff0c;以后有什么界面需要翻译&#xff0c;再更新一下。 放在这里留个…...

Linux510 ssh服务 ssh连接

arning: Permanently added ‘11.1.1.100’ (ECDSA) to the list of known hosts. rooot11.1.1.100’s password: Permission denied, please try again. rooot11.1.1.100’s password: Permission denied, please try again 还没生效 登不上了 失效了 sshcaozx26成功登录 …...

【25软考网工】第六章(2)信息加密技术

博客主页&#xff1a; christine-rr-CSDN博客 ​专栏主页&#xff1a; 软考中级网络工程师笔记 ​ 大家好&#xff0c;我是christine-rr !目前《软考中级网络工程师》专栏已经更新二十多篇文章了&#xff0c;每篇笔记都包含详细的知识点&#xff0c;希望能帮助到你&#xff01…...

LeetCode 热题 100 138. 随机链表的复制

LeetCode 热题 100 | 138. 随机链表的复制 大家好&#xff0c;今天我们来解决一道经典的链表问题——随机链表的复制。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求深拷贝一个带有随机指针的链表。 问题描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额…...

差动讯号(3)弱耦合与强耦合

各位在设计高速差动对时&#xff0c;除了阻抗之外&#xff0c;可能还会被问到一个问题&#xff0c;P与N之间的间距要多少&#xff1f; 在差动讯号&#xff08;2&#xff09;&#xff1a;奇模与偶模一文中&#xff0c;我们已经知道差动对两线间距会影响其特性阻抗&#xff0c;且…...

强化学习系列:深度强化学习和DQN

1. 往期回顾 介绍了强化学习的基本概念和基本原理 介绍了基于动态规划的传统强化学习——价值迭代、策略迭代 介绍了在无模型的环境下&#xff0c;基于时序差分的表格型强化学习——Q-learning、SARSA 这些传统的方法都有各自的局限性&#xff0c;能适用的范围有限&#xf…...

AlimaLinux设置静态IP

通过nmcli命令来操作 步骤 1&#xff1a;确认当前活动的网络接口名称 首先&#xff0c;需要确认当前系统中可用的网络接口名称。可以使用以下命令查看&#xff1a; nmcli device步骤 2&#xff1a;修改配置以匹配正确的接口名称 sudo nmcli connection modify ens160 ipv4.…...

神经网络极简入门技术分享

1. 引言 神经网络是深度学习的基础&#xff0c;其设计灵感来源于人脑神经元的结构和工作方式。尽管现代神经网络已经变得异常复杂&#xff0c;但其核心原理却相对简单易懂。本报告旨在通过剖析神经网络的最基本单元——神经元&#xff0c;帮助初学者理解神经网络的工作原理。 …...

使用定时器监视当前PID 如果当前程序关闭 UI_Core.exe 也随之自动关闭实现方法

使用定时器监视当前PID 如果当前程序关闭 UI_Core.exe 也随之自动关闭实现方法 描述: C20 QT6.9 VS2022 中使用QProcess::startDetached(“UI_Core.exe”, QStringList(), QString(), &UI_Manage_pid);是启动目标程序 能否同时告诉目标程序当前宿主程序的PID,在UI_CORE.EX…...

SpringCloud之Ribbon基础认识-服务负载均衡

0、Ribbon基本认识 Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端 负载均衡的工具。 Ribbon 主要功能是提供客户端负载均衡算法和服务调用 Ribbon 客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等。 Ribbon 会基于某种规则&#xff08;如简单…...

leetcode0829. 连续整数求和-hard

1 题目&#xff1a; 连续整数求和 官方标定难度&#xff1a;难 给定一个正整数 n&#xff0c;返回 连续正整数满足所有数字之和为 n 的组数 。 示例 1: 输入: n 5 输出: 2 解释: 5 2 3&#xff0c;共有两组连续整数([5],[2,3])求和后为 5。 示例 2: 输入: n 9 输出: …...

Python-77:古生物DNA序列血缘分析

问题描述 小U是一位古生物学家&#xff0c;正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近&#xff0c;她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成&#xff0c;并且可能通过三种方式发生变异&#xff1a;添加一个核苷酸、删除一个核苷酸或替换一…...

数据结构算法习题通关:树遍历 / 哈夫曼 / 拓扑 / 哈希 / Dijkstra 全解析

已知一棵二叉树先序遍历和中序遍历分别为 ABDEGCFH 和 DBGEACHF&#xff0c;请画出这个二叉树的逻辑结构并写出后序遍历的序列。 先序遍历&#xff1a;ABDEGCFH 中序遍历&#xff1a;DBGEACHF 先序遍历看出根为A&#xff0c;左子树DBGE&#xff0c;右子树CHF A的左子树 再…...

使用lldb查看Rust不同类型的结构

目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的&#xff0c;可以查看不同类型的结构&#xff0c;虽然这好像是C的东…...

M0的基础篇之PWM学习

一、困惑 上一节课就是单纯的之配置了一个基础的定时器进行计数&#xff0c;计到一定的数值也就是到了一定的时间就进入中断&#xff0c;执行中断里面的任务&#xff0c;也就是一个最基础的定时的功能 这一节课的定时器产生了一个pwm波。也就是我们可以改变里面高电平的持续时间…...

win10-启动django项目时报错

前提 win10系统下已经安装了pip 和django&#xff08;因为搜报错解决办法的时候&#xff0c;有博客说先检查下django有没有安装&#xff09;&#xff0c;另外也没有安装anaconda&#xff0c;没有用虚拟环境 报错如下 在pycharm执行新建app的命令python mange.py startapp app02…...

coze工作流完成行业调研报告

一、coze 是什么&#xff1f; Coze是由字节跳动推出的新一代AI应用开发平台&#xff0c;定位是零代码或低代码的AI开发平台&#xff0c;也被称为字节跳动版的GPTs &#xff0c;国内版名为“扣子”。 Coze有国内版和国外版两个版本。国内版网址为http://www.coze.cn &#xff…...

为什么有了BST了,还要红黑树,红黑树有什么优点

BST&#xff08;二叉搜索树&#xff09;和红黑树都是常见的树形数据结构&#xff0c;但红黑树在某些方面对BST进行了优化&#xff0c;主要解决了BST在特定情况下可能出现的性能问题。以下是红黑树的核心优点及其存在的必要性&#xff1a; BST的局限性 BST的时间复杂度与树的高…...

【Linux基础】网络相关命令

目录 netstat命令 1.1 命令介绍 1.2 命令格式 1.3 常用选项 1.4 常用命令实例 1.4.1 显示所有TCP连接 1.4.2 查看路由表 1.4.3 实时监控网络接口流量 1.4.4 查看监听中的端口以及关联进程 ping命令 2.1 命令介绍 2.2 命令格式 2.3 常用选项 2.4 常用示例 ifconfi…...

DB4S:一个开源跨平台的SQLite数据库管理工具

DB Browser for SQLite&#xff08;DB4S&#xff09;是一款开源、跨平台的 SQLite 数据库管理工具&#xff0c;用于创建、浏览和编辑 SQLite 以及 SQLCipher 数据库文件。 功能特性 DB4S 提供了一个电子表格风格的数据库管理界面&#xff0c;以及一个 SQL 查询工具。DB4S 支持…...

多个python环境下,pip安装无法成功解决方案

问题 使用pip install xxx&#xff0c;安装过程很顺利且无任何报错&#xff0c;但是一旦在python中import xxx时&#xff0c;仍然提示xxx不存在。 解决方案 首先排除掉xxx包命名是否正确—— 这个非本文重点。 当已经确认xxx包命名正确&#xff0c;且常规通过pip install 即…...

人脸真假检测:SVM 与 ResNet18 的实战对比

在人工智能蓬勃发展的当下&#xff0c;人脸相关技术广泛应用于安防、金融、娱乐等诸多领域。然而&#xff0c;随着人脸合成技术的日益成熟&#xff0c;人脸真假检测成为保障这些应用安全的关键环节。本文将深入探讨基于支持向量机&#xff08;SVM&#xff09;结合局部二值模式&…...

求数组中的两数之和--暴力/哈希表

暴力法太好用了hhhhhhhhhhhhhhhhhhh我好爱鹅鹅鹅鹅鹅鹅呃呃呃呃呃呃呃呃呃呃 #include <iostream> #include <vector> using namespace std; int main(){ int n,target; cin>>n>>target; vector<int> nums(n); for(int i0;i<n;i){ cin>>…...

Go多服务项目结构优化:为何每个服务单独设置internal目录?

文章目录 Go多服务项目结构优化&#xff1a;为何每个服务单独设置internal目录&#xff1f;背景什么是 Go 的 internal 机制&#xff1f;传统根 internal 目录的局限为什么要每个服务单独设置 internal &#xff1f;推荐结构示例 总结 Go多服务项目结构优化&#xff1a;为何每个…...

Wallcraft 3.53.0 | 提供高质量动态4D壁纸,解锁高级版,无广告干扰

Wallcraft是一款专注于提供高质量、原创壁纸的应用程序&#xff0c;特别是其特色的动态4D壁纸。这款应用程序不仅提供了大量免费的4K超高清壁纸和炫酷背景&#xff0c;还特别推出了带有视差效果的动态超高清4K壁纸及视频壁纸。用户可以根据个人喜好选择并设置这些壁纸作为手机屏…...

akshare爬虫限制,pywencai频繁升级个人做量化,稳定数据源和券商的选择

做量化&#xff0c;数据和交易接口是策略和自动化交易的基石&#xff0c;而稳定的数据和快人一步的交易接口是个人做量化的催化剂。 之前写过一篇文章&#xff1a;个人做量化常用的数据&#xff0c;多以爬虫为主&#xff0c;最近akshare爬虫限制&#xff0c;pywencai频繁升级。…...

leetcode504.七进制数

标签&#xff1a;进制转换 机试真题 给定一个整数 num&#xff0c;将其转化为 7 进制&#xff0c;并以字符串形式输出。 示例 1: 输入: num 100 输出: "202" 示例 2: 输入: num -7 输出: "-10" 思路&#xff1a;求n进制就是循环取余数&#xff0c;…...

OpenAI 结构改革:迈向民主化 AI 的新篇章

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

Satori:元动作 + 内建搜索机制,让大模型实现超级推理能力

Satori&#xff1a;元动作 内建搜索机制&#xff0c;让大模型实现超级推理能力 论文大纲一、背景&#xff1a;LLM 推理增强的三类方法1. 基于大规模监督微调&#xff08;SFT&#xff09;的推理增强2. 借助外部机制在推理时进行搜索 (RLHF / 多模型 / 工具)3. 现有局限性总结 二…...

Python序列化的学习笔记

1. Npy&Numpy O4-mini-Cursor&#xff1a;如果.npy文件里包含了「Python对象」而非纯数值数组时&#xff0c;就必须在加载时加上allow_pickleTrue。...

如何修改进程优先级?

文章目录 1. 摘要2. 命令实现2.1 使用 renice&#xff08;调整普通进程的优先级&#xff09;​2.2 使用 chrt&#xff08;调整实时进程的优先级&#xff09; 3. 代码实现 1. 摘要 在实际开发中&#xff0c;我们经常会遇到创建进程的场景&#xff0c;但是往往并不关心它的优先级…...

java命令行打包class为jar并运行

1.创建无包名类: 2.添加依赖jackson 3.引用依赖包 4.命令编译class文件 生成命令: javac -d out -classpath lib/jackson-core-2.13.3.jar:lib/jackson-annotations-2.13.3.jar:lib/jackson-databind-2.13.3.jar src/UdpServer.java 编译生成class文件如下 <...

JAVA自动装箱拆箱

引言 Java 中的**装箱&#xff08;Boxing&#xff09;和拆箱&#xff08;Unboxing&#xff09;**是自动类型转换的机制&#xff0c;用于在基本数据类型&#xff08;如 int、long 等&#xff09;和其对应的包装类&#xff08;如 Integer、Long 等&#xff09;之间进行转换。这种…...

Linux系统之----模拟实现shell

在前面一个阶段的学习中&#xff0c;我们已经学习了环境变量、进程控制等等一系列知识&#xff0c;也许有人会问&#xff0c;学这个东西有啥用&#xff1f;那么&#xff0c;今天我就和大家一起综合运用一下这些知识&#xff0c;模拟实现下shell&#xff01; 首先我们来看一看我…...

Doris和Clickhouse对比

目录 一、Doris和Clickhouse对比**1. 底层架构****Doris****ClickHouse** **2. 运行原理****Doris****ClickHouse** **3. 使用场景****Doris****ClickHouse** **4. 优缺点对比****总结** 二、MPP架构和Shared-Nothing 架构对比**1. 什么是 MPP 架构&#xff1f;****定义****特点…...

思考:(linux) tmux 超级终端快速入门的宏观思维

tmux 工具集合 GitHub - rothgar/awesome-tmux: A list of awesome resources for tmux 要点&#xff1a; 习惯性思维的变换与宿主机之间的双向复制、粘贴手动备份全部窗口&#xff0c;以及还原自定义窗格提示信息TPM 插件的安装思想别名 在有些场景里&#xff0c;可能无法…...

JavaScript基础-全局作用域

在JavaScript中&#xff0c;理解不同种类的作用域是掌握这门语言的关键之一。作用域决定了变量和函数的可访问性&#xff08;即可见性和生命周期&#xff09;。其中&#xff0c;全局作用域是最基本也是最宽泛的作用域类型。本文将深入探讨全局作用域的概念、特点及其使用时需要…...

【MCAL】TC397+EB-tresos之I2c配置实战(同步、异步)

I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线&#xff0c;主要用于近距离、低速的芯片之间的通信。本篇文章首先从理论讲起&#xff0c;介绍了英飞凌TC3x系列芯片对应MCAL中对I2C驱动的定义与介绍&#xff0c;建议读者在阅读本篇文章之前对I2C有个简单的认识…...

电网拓扑分析:原理与应用

在现代电力系统中&#xff0c;电网拓扑分析是一项至关重要的技术&#xff0c;它为电力系统的安全、稳定和高效运行提供了坚实的基础。电网拓扑描述了电力系统中各元件&#xff08;如发电机、变压器、输电线路、负荷等&#xff09;之间的连接关系&#xff0c;通过拓扑分析&#…...

leetcode-hot-100(哈希)

写在前面 这部分官方标记为哈希&#xff0c;下面的代码使用的都是 C 进行实现&#xff0c;说到 C 中的哈希&#xff0c;需要了解一下 C 中的 hashtable&#xff08;std::unordered_map或std::unordered_set&#xff09;。 std::unordered_map std::unordered_map 是一个存储…...

音频类网站或者资讯总结

我爱音频网&#xff1a; 我爱音频网 - 我们只谈音频&#xff0c;丰富的TWS真无线蓝牙耳机拆解报告 (52audio.com) 其他更多资讯 音频行业全品类深度剖析&#xff0c;2024市场趋势解读汇总-EDN 电子技术设计 (ednchina.com)...

优选算法——前缀和

目录 1. 数组的中心下标 2. 除自身以外数组的乘积 3. 和为k的子数组 4. 和可被K整除的子数组 5. 连续数组 6. 矩阵区域和 1. 数组的中心下标 题目链接&#xff1a;724. 寻找数组的中心下标 - 力扣&#xff08;LeetCode&#xff09; 题目展示&#xff1a; 题目分析&am…...