深入理解Java中的线程状态转换及关键同步方法

news2024/11/15 19:24:21

深入理解Java中的线程状态转换及关键同步方法

在现代软件开发中,多线程编程是实现高效、响应式应用程序的关键技术之一。理解线程的生命周期及其状态转换,对于编写健壮、性能优越的并发程序至关重要。本文将深入探讨Java中线程的各种状态转换,并详细解释joinsleepwaitpark等关键方法的作用与使用场景。

目录

  1. Java线程概述
  2. 线程状态详解
    • NEW(新建状态)
    • RUNNABLE(可运行状态)
    • BLOCKED(阻塞状态)
    • WAITING(等待状态)
    • TIMED_WAITING(计时等待状态)
    • TERMINATED(终止状态)
  3. 线程状态转换图
  4. 关键同步方法解析
    • join() 方法
    • sleep() 方法
    • wait() 方法
    • park() 方法
    • interrupt 方法
  5. 示例代码
  6. 最佳实践与注意事项
  7. 总结

Java线程概述

在Java中,线程是执行程序中最小的执行单元。Java通过java.lang.Thread类和java.lang.Runnable接口提供了对线程的支持。线程允许程序并行执行多个任务,从而提升应用程序的性能和响应性。

线程的生命周期由多个状态组成,理解这些状态及其转换对于掌握多线程编程至关重要。


线程状态详解

Java为线程定义了六种状态,通过java.lang.Thread.State枚举类来表示:

  1. NEW(新建状态)
  2. RUNNABLE(可运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(等待状态)
  5. TIMED_WAITING(计时等待状态)
  6. TERMINATED(终止状态)

NEW(新建状态)

  • 描述:线程对象已被创建,但尚未启动。
  • 触发条件:通过new Thread()创建一个线程对象后,线程处于NEW状态。
Thread thread = new Thread(() -> {
    // 线程任务
});
System.out.println(thread.getState()); // 输出: NEW

RUNNABLE(可运行状态)

  • 描述:线程已经启动,并在Java虚拟机中准备运行,或正在运行。
  • 触发条件:调用Thread.start()方法后,线程进入RUNNABLE状态。
  • 注意RUNNABLE状态包括正在运行(实际获得CPU时间片)和等待调度的情况。
thread.start();
System.out.println(thread.getState()); // 输出: RUNNABLE

BLOCKED(阻塞状态)

  • 描述:线程试图获取一个对象的监视器锁(锁竞争),无法继续执行。
  • 触发条件:线程进入RUNNABLE状态后,试图获取一个被其他线程锁定的对象锁。
synchronized(lock) {
    // 线程A持有锁
}

Thread threadB = new Thread(() -> {
    synchronized(lock) {
        // 线程B试图获取锁,若锁被线程A持有,则进入BLOCKED状态
    }
});
threadB.start();

WAITING(等待状态)

  • 描述:线程无限期地等待另一个线程来执行特定操作。
  • 触发条件
    • 调用Object.wait()方法,没有指定超时时间。
    • 调用Thread.join()方法,没有指定超时时间。
    • 调用LockSupport.park()方法。
synchronized(lock) {
    lock.wait(); // 线程进入WAITING状态
}

TIMED_WAITING(计时等待状态)

  • 描述:线程在等待另一个线程的特定操作,且等待时间有限。
  • 触发条件
    • 调用Object.wait(long timeout)方法,指定超时时间。
    • 调用Thread.sleep(long millis)方法。
    • 调用Thread.join(long millis)方法,指定超时时间。
    • 调用LockSupport.parkNanos(long nanos)LockSupport.parkUntil(long deadline)方法。
Thread.sleep(1000); // 线程进入TIMED_WAITING状态

TERMINATED(终止状态)

  • 描述:线程已经完成执行或因异常而终止。
  • 触发条件:线程的run()方法执行完毕或因未捕获的异常导致线程终止。
thread.join();
System.out.println(thread.getState()); // 输出: TERMINATED

线程状态转换图

在这里插入图片描述


关键同步方法解析

理解线程状态转换离不开对关键同步方法的掌握。下面详细解释joinsleepwaitpark等方法的作用及其对线程状态的影响。

join() 方法

详细解析

join()方法用于让一个线程等待另一个线程完成执行。它有三个重载版本:

  1. join():无限期等待,直到目标线程终止。
  2. join(long millis):等待指定的毫秒数,或者直到目标线程终止。
  3. join(long millis, int nanos):等待指定的毫秒数和纳秒数,或者直到目标线程终止。
示例
public class JoinExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(2000); // 模拟任务
                System.out.println("子线程执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread.start();

        try {
            thread.join(); // 等待子线程完成
            System.out.println("主线程继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出

子线程执行完毕
主线程继续执行
关键点
  • join()确保了线程的顺序执行。
  • 可以通过join(long millis)控制等待时间,防止线程永久阻塞。

sleep() 方法

详细解析

sleep()方法使当前线程暂停执行指定的时间。它有两个重载版本:

  1. sleep(long millis):暂停指定的毫秒数。
  2. sleep(long millis, int nanos):暂停指定的毫秒数和纳秒数。
示例
public class SleepExample {
    public static void main(String[] args) {
        System.out.println("主线程开始睡眠");
        try {
            Thread.sleep(1000); // 睡眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程醒来");
    }
}

输出

主线程开始睡眠
主线程醒来
关键点
  • sleep()不会释放锁,即使在synchronized块中调用。
  • sleep()是静态方法,作用于当前线程。

wait() 方法

详细解析

wait()方法使当前线程等待,直到被其他线程调用notify()notifyAll()方法。它有三个重载版本:

  1. wait():无限期等待。
  2. wait(long timeout):等待指定的毫秒数。
  3. wait(long timeout, int nanos):等待指定的毫秒数和纳秒数。
示例
public class WaitNotifyExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread consumer = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("消费者等待数据...");
                    lock.wait(); // 进入WAITING状态
                    System.out.println("消费者收到通知,继续执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread producer = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(2000); // 模拟数据生成
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产者生成数据,通知消费者");
                lock.notify(); // 唤醒消费者线程
            }
        });

        consumer.start();
        producer.start();
    }
}

输出

消费者等待数据...
生产者生成数据,通知消费者
消费者收到通知,继续执行
关键点
  • wait()必须在synchronized块或方法内调用,确保线程持有对象锁。
  • 调用wait()会释放对象锁,允许其他线程进入synchronized块。
  • notify()notifyAll()唤醒后,线程会重新竞争锁,获取锁后继续执行。

park() 方法

详细解析

park()方法是LockSupport类提供的高级线程阻塞机制,允许线程被阻塞,直到被其他线程调用unpark()或被中断。它有两种主要形式:

  1. park():无限期等待。
  2. parkNanos(long nanos)parkUntil(long deadline):有时间限制的等待。
示例
import java.util.concurrent.locks.LockSupport;

public class ParkUnparkExample {
    public static void main(String[] args) throws InterruptedException {
        Thread parker = new Thread(() -> {
            System.out.println("Parker 线程被阻塞");
            LockSupport.park(); // 进入 WAITING 状态
            System.out.println("Parker 线程被唤醒");
        });

        parker.start();

        Thread.sleep(1000); // 主线程等待1秒
        System.out.println("主线程调用 unpark() 唤醒 Parker 线程");
        LockSupport.unpark(parker);
    }
}

输出

Parker 线程被阻塞
主线程调用 unpark() 唤醒 Parker 线程
Parker 线程被唤醒
关键点
  • park()unpark()是配对使用的,unpark()可以在park()之前调用,导致park()不阻塞。
  • 不需要在synchronized块内调用,可以灵活地用于复杂的并发控制。
  • park()不会抛出异常,必须通过其他机制(如中断状态)来控制线程。

interrupt 方法

详细解析

在Java中,interrupt机制用于中断线程的阻塞状态。通过调用线程的interrupt()方法,可以向线程发送一个中断信号,通知线程它应该停止当前的阻塞操作并尽快退出。

interrupt 的作用
  • 中断阻塞状态的线程:当线程处于WAITINGTIMED_WAITINGBLOCKED状态时,调用interrupt()会使线程抛出InterruptedException,从而打断阻塞状态。
  • 设置中断状态:如果线程不处于阻塞状态,调用interrupt()会设置线程的中断状态标志,但不会立即中断线程的执行。
isInterrupted()interrupted() 方法的区别

Java中提供了两个方法来检查线程的中断状态:isInterrupted()interrupted()。它们的区别如下:

  1. isInterrupted() 方法

    • 用途:检查线程是否被中断。
    • 行为:不会清除中断状态。
    • 使用场景:适用于需要多次检查中断状态的情况。
    • 示例
    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行任务
        }
        System.out.println("线程被中断,退出循环");
    });
    
    thread.start();
    thread.interrupt();
    
  2. interrupted() 方法

    • 用途:检查当前线程是否被中断。
    • 行为:会清除当前线程的中断状态。
    • 使用场景:适用于只需要一次性检查中断状态的情况。
    • 示例
    Thread thread = new Thread(() -> {
        while (!Thread.interrupted()) {
            // 执行任务
        }
        System.out.println("线程被中断,退出循环");
    });
    
    thread.start();
    thread.interrupt();
    
示例
public class InterruptExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("线程开始睡眠");
                Thread.sleep(5000); // 进入 TIMED_WAITING 状态
            } catch (InterruptedException e) {
                System.out.println("线程被中断,捕获 InterruptedException");
            }

            // 检查中断状态
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("线程中断状态为 true");
            } else {
                System.out.println("线程中断状态为 false");
            }

            // 使用 interrupted() 方法
            boolean interrupted = Thread.interrupted();
            System.out.println("调用 interrupted() 后,中断状态为: " + interrupted);
        });

        thread.start();

        try {
            Thread.sleep(1000); // 主线程等待1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程调用 interrupt() 中断子线程");
        thread.interrupt();
    }
}

输出

线程开始睡眠
主线程调用 interrupt() 中断子线程
线程被中断,捕获 InterruptedException
线程中断状态为 false
调用 interrupted() 后,中断状态为: false

解释

  1. 子线程开始执行并进入睡眠状态,进入TIMED_WAITING
  2. 主线程等待1秒后调用interrupt()中断子线程。
  3. 子线程在睡眠期间被中断,抛出InterruptedException并捕获。
  4. 捕获异常后,子线程检查中断状态,发现已经被清除(isInterrupted()返回false)。
  5. 调用Thread.interrupted()再次检查中断状态,返回false,并清除中断状态标志。
关键点
  • isInterrupted()
    • 检查线程的中断状态,不会清除状态。
    • 适用于需要多次检查的场景。
  • interrupted()
    • 检查当前线程的中断状态,并清除状态。
    • 适用于一次性检查的场景。

方法对比总结

方法所属类用途阻塞状态释放锁唤醒方式其他特点
join()java.lang.Thread等待另一个线程完成执行WAITING / TIMED_WAITING目标线程终止或超时用于线程顺序控制,主线程等待子线程
sleep()java.lang.Thread暂停当前线程执行指定时间TIMED_WAITING睡眠时间到达或被中断不依赖于任何锁,常用于定时任务
wait()java.lang.Object等待其他线程的通知WAITING / TIMED_WAITINGnotify()notifyAll()必须在synchronized块内调用,释放锁
park()java.util.concurrent.locks.LockSupport阻塞当前线程,直到被unpark唤醒WAITING / TIMED_WAITINGunpark() 或被中断更灵活,非synchronized,可用于高级并发工具
interrupt()java.lang.Thread中断线程的阻塞状态或设置中断标志触发阻塞线程抛出InterruptedExceptioninterrupt()调用者可以用于打断阻塞状态,设置中断标志

详细对比

  1. 阻塞条件与唤醒方式

    • join():依赖于另一个线程的终止状态或超时自动唤醒。
    • sleep():基于时间自动唤醒,不依赖于任何外部事件。
    • wait():依赖于其他线程的notify()notifyAll()调用。
    • park():依赖于其他线程的unpark()调用或线程被中断。
    • interrupt():通过调用interrupt()方法可以中断正在阻塞的线程,如wait()sleep()park()等。
  2. 锁的释放

    • wait():在调用时会释放对象锁,允许其他线程进入synchronized块。
    • join()sleep()park()interrupt():不释放任何锁。
  3. 调用环境

    • wait():必须在synchronized块或方法内调用。
    • join()sleep()park()interrupt():不需要在synchronized块内调用(除了wait()必须在同步环境中)。
  4. 灵活性与适用场景

    • join():适用于线程间顺序执行的场景。
    • sleep():适用于需要暂停线程执行的场景,如定时任务或延时操作。
    • wait():适用于线程间需要等待特定条件的场景,如生产者-消费者模型。
    • park():适用于需要更灵活控制线程阻塞与唤醒的高级并发场景,通常在构建并发框架时使用。
    • interrupt():适用于需要中断阻塞线程或设置中断标志的场景,用于控制线程的执行流程。

示例代码

以下示例代码展示了joinsleepwaitparkinterrupt方法的区别与使用。

public class SyncMethodsComparison {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 示例1:join()
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("Thread1 完成执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread1.join(); // 主线程等待 thread1 完成
        System.out.println("主线程在 join() 后继续执行");

        // 示例2:sleep()
        Thread thread2 = new Thread(() -> {
            try {
                System.out.println("Thread2 开始睡眠");
                Thread.sleep(2000);
                System.out.println("Thread2 睡眠结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread2.start();
        thread2.join(); // 等待 thread2 完成
        System.out.println("主线程在 sleep() 后继续执行");

        // 示例3:wait() 和 notify()
        Thread consumer = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("消费者等待数据...");
                    lock.wait(); // 进入 WAITING 状态
                    System.out.println("消费者收到通知,继续执行");
                } catch (InterruptedException e) {
                    System.out.println("消费者被中断");
                }
            }
        });

        Thread producer = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟数据生成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println("生产者生成数据,通知消费者");
                lock.notify(); // 唤醒消费者线程
            }
        });

        consumer.start();
        producer.start();

        consumer.join();
        producer.join();
        System.out.println("主线程在 wait()/notify() 后继续执行");

        // 示例4:park() 和 unpark()
        Thread parker = new Thread(() -> {
            System.out.println("Parker 线程被阻塞");
            LockSupport.park(); // 进入 WAITING 状态
            System.out.println("Parker 线程被唤醒");
        });

        parker.start();
        Thread.sleep(1000); // 主线程等待1秒
        System.out.println("主线程调用 unpark() 唤醒 Parker 线程");
        LockSupport.unpark(parker);

        parker.join();
        System.out.println("主线程在 park()/unpark() 后继续执行");

        // 示例5:interrupt() 方法
        Thread interrupter = new Thread(() -> {
            try {
                System.out.println("Interrupter 线程开始睡眠");
                Thread.sleep(5000); // 进入 TIMED_WAITING 状态
            } catch (InterruptedException e) {
                System.out.println("Interrupter 线程被中断,捕获 InterruptedException");
            }

            // 检查中断状态
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupter 线程中断状态为 true");
            } else {
                System.out.println("Interrupter 线程中断状态为 false");
            }

            // 使用 interrupted() 方法
            boolean interrupted = Thread.interrupted();
            System.out.println("调用 interrupted() 后,中断状态为: " + interrupted);
        });

        interrupter.start();
        Thread.sleep(1000); // 主线程等待1秒
        System.out.println("主线程调用 interrupt() 中断 interrupter 线程");
        interrupter.interrupt();

        interrupter.join();
        System.out.println("主线程在 interrupt() 后继续执行");
    }
}

输出

Thread1 完成执行
主线程在 join() 后继续执行
Thread2 开始睡眠
Thread2 睡眠结束
主线程在 sleep() 后继续执行
消费者等待数据...
生产者生成数据,通知消费者
消费者收到通知,继续执行
主线程在 wait()/notify() 后继续执行
Parker 线程被阻塞
主线程调用 unpark() 唤醒 Parker 线程
Parker 线程被唤醒
主线程在 park()/unpark() 后继续执行
Interrupter 线程开始睡眠
主线程调用 interrupt() 中断 interrupter 线程
Interrupter 线程被中断,捕获 InterruptedException
Interrupter 线程中断状态为 false
调用 interrupted() 后,中断状态为: false
主线程在 interrupt() 后继续执行

解释

  1. join() 示例

    • 主线程启动thread1并调用join(),主线程等待thread1完成后继续执行。
  2. sleep() 示例

    • 主线程启动thread2thread2进入睡眠2秒,醒来后完成执行。
    • 主线程通过join()等待thread2完成,然后继续执行。
  3. wait()notify() 示例

    • consumer线程进入synchronized块并调用wait(),进入WAITING状态。
    • producer线程等待1秒后进入synchronized块,调用notify()唤醒consumer线程。
    • consumer线程被唤醒后继续执行。
  4. park()unpark() 示例

    • parker线程调用LockSupport.park()进入WAITING状态。
    • 主线程等待1秒后调用LockSupport.unpark(parker)唤醒parker线程。
    • parker线程被唤醒后继续执行。
  5. interrupt() 方法示例

    • interrupter线程开始睡眠5秒,进入TIMED_WAITING状态。
    • 主线程等待1秒后调用interrupter.interrupt()中断interrupter线程。
    • interrupter线程在睡眠期间被中断,抛出InterruptedException并捕获。
    • 捕获异常后,interrupter线程检查中断状态,发现已被清除(isInterrupted()返回false)。
    • 调用Thread.interrupted()再次检查中断状态,返回false,并清除中断状态标志。

最佳实践与注意事项

  1. 避免死锁

    • 确保多个线程获取锁的顺序一致。
    • 尽量减少锁的持有时间。
    • 使用超时机制(如tryLock)防止长时间等待锁。
  2. 合理使用synchronized

    • 只在必要的代码块使用synchronized,避免过度同步导致性能下降。
    • 优先使用更高级的并发工具,如ReentrantLock,以获得更灵活的锁控制。
  3. 理解wait()notify()的配合使用

    • wait()notify()必须在synchronized块内调用,确保线程持有对象锁。
    • 使用while循环检查条件,以防止虚假唤醒。
    synchronized (lock) {
        while (!condition) {
            lock.wait();
        }
        // 继续执行
    }
    
  4. 合理使用park()unpark()

    • LockSupport提供了更灵活的线程阻塞机制,但需要谨慎使用,避免线程永远被阻塞。
  5. 使用join()确保线程执行顺序

    • 在需要等待某个线程完成后再继续执行的场景下,使用join()方法确保执行顺序。
  6. 避免使用过期的并发工具

    • 尽量使用Java提供的高级并发工具(如CountDownLatchSemaphoreCyclicBarrier等)代替低级别的synchronizedwait/notify机制,以提高代码的可读性和可维护性。
    CountDownLatch latch = new CountDownLatch(1);
    
    Thread worker = new Thread(() -> {
        // 处理任务
        latch.countDown(); // 任务完成,减少计数
    });
    
    worker.start();
    
    // 等待任务完成
    latch.await();
    
  7. 处理中断

    • 在可能阻塞的方法(如wait()sleep()park())中,正确处理中断信号,确保线程能够及时响应中断。
    • 避免忽略InterruptedException,应根据业务需求决定如何处理中断。
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // 处理中断,如清理资源、退出线程等
        Thread.currentThread().interrupt(); // 重新设置中断状态
    }
    

总结

理解Java中线程的状态及其转换机制,是掌握多线程编程的基础。通过熟悉NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED等线程状态,以及joinsleepwaitparkinterrupt等关键方法的作用与使用场景,开发者可以更高效地设计和实现并发程序。

正确理解和使用这些同步方法,能够帮助开发者更好地控制线程行为,避免常见的并发问题,如死锁、竞态条件等。同时,结合Java提供的高级并发工具,可以编写出更高效、可维护的多线程程序。

希望本文能够帮助你深入理解Java中的线程状态转换及关键同步方法,并在实际开发中灵活应用。如有任何问题或需要进一步探讨,欢迎在评论区留言交流!

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

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

相关文章

探索信号处理:使用傅里叶小波变换分析和恢复信号

在现代信号处理领域,傅里叶变换是分析和处理信号的一种基本工具。然而,传统的傅里叶变换在处理非平稳信号时存在局限性,因为它无法同时提供时间和频率的信息。为了克服这一挑战,傅里叶小波变换(FSWT)应运而…

爆改YOLOv8|使用MobileNetV4替换yolov8的Backbone

1,本文介绍 MobileNetV4 是最新的 MobileNet 系列模型,专为移动设备优化。它引入了通用反转瓶颈(UIB)和 Mobile MQA 注意力机制,提升了推理速度和效率。通过改进的神经网络架构搜索(NAS)和蒸馏…

java项目之中药实验管理系统(源码+文档)

项目简介 中药实验管理系统实现了以下功能: 中药实验管理系统的主要使用者管理员功能有个人中心,学生管理,教师管理,实验员管理,实验教学管理,在线学习管理,实验信息管理,实验预约…

C++速通LeetCode简单第16题-买卖股票的最佳时机

思路要点&#xff1a;假设当天卖&#xff0c;动态更新最低价格和最大利益 class Solution { public://要点&#xff1a;假设当天卖&#xff0c;动态更新最低价格和最大利益int maxProfit(vector<int>& prices) {int ans 0;int lowest prices[0];for(int i 1; i &…

bin | hex

hex bin 纯粹的程序数据&#xff0c;不包含&#xff1a;长度、地址、数据等信息 可以直接把数据赋值到单片机程序地址&#xff08;STM32 一般时0x8000 0000&#xff09; 应用场景 远程固件升级 比较 实际文件对比

Mybatis-plus-Generator 3.5.5 自定义模板支持 (DTO/VO 等) 配置

随着项目节奏越来越快&#xff0c;为了减少把时间浪费在新建DTO 、VO 等地方&#xff0c;直接直接基于Mybatis-plus 这颗大树稍微扩展一下&#xff0c;在原来生成PO、 DAO、Service、ServiceImpl、Controller 基础新增。为了解决这个问题&#xff0c;网上找了一堆资料&#xff…

【硬件模块】SHT20温湿度传感器

SHT20是一个用IIC通信的温湿度传感器。我们知道这个就可以了。 它支持的电压范围是2.1~3.6V&#xff0c;推荐是3V&#xff0c;所以如果我们的MCU是5V的&#xff0c;那么就得转个电压才能用了。 IIC常见的速率有100k&#xff0c;400k&#xff0c;而SHT20是支持400k的&#xff08…

Parallels Desktop 20破解版(Mac虚拟机) v20.0.0 for Mac 最新商业版(支持M系列)

Parallels Desktop 20是一款目前功能最强大灵活度最高的虚拟机软件&#xff0c;可运行数千种 Windows 应用程序&#xff0c;如 Microsoft Office、Internet Explorer、Access、Quicken、QuickBooks、Visual Studio&#xff0c;甚至支持对图像要求较高的游戏和 CAD 项目&#xf…

[产品管理-17]:NPDP新产品开发 - 15 - 产品设计与开发工具 - 工欲善其事,必先利其器 - 创意工具:借助各种工具和方法,完成产品的创意

目录 前言&#xff1a; 一、创意&#xff08;用户问题 -》产品想法&#xff09;的生成 1.1 创意 1.2 先发散后收敛 1.3 创意工具集 二、创意工具概述 2.1 SCAMPER - 按照数据处理逻辑运算的方式发散 SCAMPER创意工具详解&#xff1a;对数据/产品的各种运算、变换&…

如何用SQL Server和Oracle进行数据同步?

数据同步是大数据应用中非常重要的环节&#xff0c;它可以保证数据的实时性和一致性&#xff0c;为数据分析和决策提供重要依据。常见的数据同步方式包括ETL实时同步和实时ETL工具&#xff0c;其中实时ETL工具又可以分为基于日志追踪和基于触发器两种。 针对不同的数据库系统&…

Hqst 品牌 H81801D 千兆 DIP 网络变压器在光猫收发器机顶盒中的应用

Hqst 牌 H81801D 千兆 DIP 网络变压器在光猫收发器机顶盒中的应用主要包括以下几个方面&#xff1a; 1. 信号匹配和转换&#xff1a; H81801D 网络变压器能够匹配光猫收发器与以太网电缆之间的阻抗差&#xff0c;确保信号在传输过程中的完整性&#xff0c;同时它还能将光信号转…

量化交易backtrader实践(一)_数据获取篇(3)_爬取数据

这一节实践其实是在上一节之前进行的&#xff0c;背景原因是因为tushare.pro的积分不够高&#xff0c;当时还没有接触到使用akshare等其他接口&#xff0c;因此对于全股票列表用的是去网页上爬的方式获得的&#xff0c;也就借此机会&#xff0c;再复习了一遍爬虫的相关知识。 …

WPS生成目录

导航窗格&#xff1a;视图->导航窗格 可修改标题的样式&#xff0c;之后的标题直接套用即可 修改其他标题样式也是这样 添加编号&#xff1a;可以选上面的模版 也可自定义编号 生成目录&#xff1a;引用->目录->选用一个 但是我想把目录插到另一页 当我添加几个标题…

IDEA-调用Restful接口

告别Swagger3/Apifox/Postman Swagger3&#xff08;丝袜哥&#xff09; 地址&#xff1a;REST API Documentation Tool | Swagger UI简介&#xff1a;在java代码里面增加注解生成接口文档 在代码里面增加注解 RestController RequestMapping("api/v1/user") Api(ta…

移动UI案例:工具类app整套案例

工具类App是指提供各种实用工具和功能的手机应用程序。这些工具可以包括但不限于日历、闹钟、备忘录、翻译、计算器、单位转换、天气预报、地图导航、音乐播放器、相机、视频编辑等。这些工具类App能够帮助用户解决日常生活和工作中的各种问题&#xff0c;提高效率和便利性。 …

浅谈Spring Cloud:认识微服务

SpringCloud就是分布式微服务架构的一站式解决方案&#xff0c;是微服务架构落地的多种技术的集合。 目录 微服务远程调用 Eureka注册中心 搭建Eureka Server 注册组件 服务拉取 当各种各样的服务越来越多&#xff0c;拆分的也越来越细&#xff0c;此时就会出现一个服务集…

Ubuntu 22.04.5 LTS 发布下载 - 现代化的企业与开源 Linux

Ubuntu 22.04.5 LTS (Jammy Jellyfish) - 现代化的企业与开源 Linux Ubuntu 22.04.5 发布&#xff0c;配备 Linux 内核 6.8 请访问原文链接&#xff1a;https://sysin.org/blog/ubuntu-2204/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xf…

Spring Boot与gRPC的完美融合:构建高效用户服务与订单服务通信

gRPC 是一种由 Google 开发的高性能、开源的远程过程调用&#xff08;Remote Procedure Call, RPC&#xff09;框架。它允许在不同的计算机系统或进程之间进行通信&#xff0c;使得分布式系统和微服务架构中的服务之间能够轻松地相互调用方法。gRPC 基于 HTTP/2 协议&#xff0…

21章 规则集和映射

1.同20章 线性表、栈、队列和优先队列的第10题。 2.同20章 线性表、栈、队列和优先队列的第1题。 3.修改程序清单21-7中的程序。如果关键字在注释或者字符串中&#xff0c;则不进行统计。将Java文件名从命令行传递。 import java.io.*; import java.util.Arrays; import jav…

轨道列车舱门检测系统源码分享

轨道列车舱门检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…