⛳ Java多线程 一,线程基础

news2024/7/2 4:04:55

线程基础

  • ⛳ Java多线程 一,线程基础
  • 🐾 一,线程基础
      • 💭 1.1,什么是程序,进程,线程
      • 🏭 1.2,什么是并行和并发
      • 👣 1.3,线程使用的场景
      • 🎨 1.4,创建线程
        • 1.4.1,继承 `Thread`类的方式
        • 1.4.2,实现`Runanble`接口:
        • 1.4.3,`Thread` 常见方法
        • 1.4.4,`sleep()`方法
        • 1.4.5,线程优先级
        • 1.4.6,守护线程
        • 1.4.7,线程合并
        • 1.4.8,线程退出
      • 📢 1.5,线程的生命周期
        • 1.5.1,线程的调度与时间片
        • 1.5.2,线程状态
        • 1.5.3,多线程自增 i++ 和线程执行原理

⛳ Java多线程 一,线程基础

当涉及多线程编程时,我们可以同时执行多个任务,从而提高程序的性能和响应性。然而,多线程编程也带来了一些挑战,比如线程同步和资源共享。在本教程中,我们将介绍Java中的多线程编程,包括创建和管理线程,线程同步,以及常见的多线程问题和解决方案。

🐾 一,线程基础

💭 1.1,什么是程序,进程,线程

在计算机科学中,程序(Program),进程(Process)和线程(Thread)是三个重要的概念,它们都与执行计算机任务和程序相关。

  1. 程序(Program):

    程序是一系列指令的集合,这些指令按照特定的顺序组织,用于完成特定的任务或执行特定的操作。程序是静态的,它们只是存储在计算机磁盘或存储设备中,并不直接执行。当我们想要运行一个程序时,操作系统会将程序加载到内存中,并将其转换为进程,然后才能执行其中的指令。

  2. 进程(Process):

    进程是计算机程序在执行时的实例。它是计算机中正在运行的程序的活动副本。每个进程都有自己独立的内存空间,包含程序代码、数据、堆栈等信息。进程之间相互隔离,一个进程的崩溃不会影响其他进程的稳定性。每个进程在操作系统中都有自己的标识符(Process ID),它可以用来管理和监控进程的状态。

    进程有以下特点:

    • 进程之间相互隔离,一个进程的崩溃不会影响其他进程的稳定性。每个进程在操作系统中都有自己的标识符(Process ID),它可以用来管理和监控进程的状态。
    • 资源分配:每个进程拥有自己的内存空间和系统资源。
    • 切换开销:在进程之间切换会导致一定的开销,因为需要保存和恢复各个进程的上下文。
    1. 线程(Thread):

      线程是进程中的执行单元,每个进程可以包含多个线程。线程与进程共享相同的内存空间,因此它们可以更轻松地相互通信。多线程在多核处理器上能够实现并行执行,从而提高程序的性能和响应性。

      线程有以下特点:

      • 共享资源:线程之间共享进程的内存和资源,可以更方便地交换数据和信息。

      • 轻量级:相较于进程,线程的创建、切换和销毁开销较小。

      • 同步问题:多线程共享资源可能导致同步问题,需要采取同步机制来避免数据冲突。

        总结:

        程序是一组指令的集合,进程是程序在执行时的实例,而线程是进程中的执行单元。进程之间相互独立,而线程共享同一进程的资源。多线程在并行处理和提高程序性能方面具有优势,但同时也需要注意处理线程同步问题。

🏭 1.2,什么是并行和并发

并行(Parallel /ˈpærəlel/ )和并发(Concurrent /kənˈkʌrənt/)是两个与计算机执行任务相关的重要概念。

  1. 并行(Parallel):

    并行是指同时执行多个任务或操作。在计算机中,当多个处理器核心或计算单元同时执行不同的任务或同一个任务的不同部分时,我们称之为并行执行。这样可以显著提高计算机系统的处理能力和效率,特别时在多核处理器上。

    举例来说,如果有一个任务要处理一系列的数据,那么在并行处理中,不同的处理器核心可以同时处理这些数据的不同部分,从而更快地完成任务。

  2. 并发(Concurrent):

    并发是指同时处理多个任务,但并不一定是同时执行这些任务。在计算机中,并发执行是通过快速的任务切换和调度来实现的,操作系统会以非常小的时间片轮流执行多个任务,让它们看起来是同时进行的。

    举例来说,如果有两个任务要处理,操作系统可以分别给它们分配时间片,让它们交替执行,从用户的角度来看,好像这两个任务在同时进行。

    关键区别: 并行是真正的同时执行多个任务,需要多个处理器核心或计算单元支持。而并发是通过快速的切换和调度,让多个任务交替执行,从用户的角度来看,好像这些任务在同时进行,但实际上是在时间上错开的。

    总结:

    并行是真正的同时执行多个任务,利用多核处理器等技术提高计算效率;而并发是通过任务切换和调度,让多个任务交替执行,实现任务间的快速切换,从用户角度看起来是同时执行的。并行适合利用多核处理器等资源来加速计算,而并发适合在单核处理器上实现任务的高效利用。

👣 1.3,线程使用的场景

线程在计算机编程和软件开发中有许多使用场景。

它们主要用于以下情况:

  1. 并发执行:线程使得程序能够同时处理多个任务,特别是在多核处理器上能够实现真正的并行执行。例如,一个计算密集型的应用程序可以将任务分成多个线程,在多核处理器上同时执行,提高整体性能。
  2. 多任务处理:线程可以用于在单个应用程序内执行多个任务,使得程序能够同时响应多个用户请求或事件。例如,一个服务器应用程序可以使用多线程来同时处理多个客户端请求。
  3. 用户界面响应:在图形用户界面(GUI)应用程序中,线程用于保持用户界面的响应性。将耗时的任务(如文件加载、网络请求等)放在后台线程中执行,以免阻塞用户界面的操作。
  4. 并行算法:在某些计算密集型任务中,可以使用线程实现并行算法,将计算任务分成多个部分并在不同线程中并行执行,从而加快计算速度。
  5. 服务器应用:在服务器端应用程序中,线程用于同时处理多个客户端连接请求,实现高并发处理能力。
  6. 多媒体处理:在音频、视频处理等应用中,线程可用于同时处理多个媒体流,实现流畅的播放和录制。
  7. 游戏开发:在游戏开发中,线程通常用于同时处理游戏逻辑、图形渲染和用户输入,以提供流畅的游戏体验。

需要注意的是,虽然线程在很多情况下能够提高程序的性能和响应性,但多线程编程也带来了一些挑战,如数据同步与竞争条件。正确地处理线程同步和资源共享问题对于编写稳定和可靠的多线程应用程序至关重要。因此,在使用线程时,开发人员需要仔细考虑并采取适当的同步措施,以避免潜在的问题。

🎨 1.4,创建线程

有两种方式可以创建线程:

1,继承Thread 类并重写 run()方法;

2,实现Runnable接口;

1.4.1,继承 Thread类的方式

  1. 定义子类继承Thread类。
  2. 类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法。

案例:

启动一个线程,在线程中执行1-10000的偶数打印工作:

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 1; i <= 10000; i++) {
            if (i % 2 == 0) {
                //Thread.currentThread().getName():得到线程的名字
                System.out.println(Thread.currentThread().getName() + "\t" + i);

            }
        }
    }
}

测试:

public class Test1 {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.start();

        MyThread myThread2 = new MyThread();
        myThread2.start();

        System.out.println(Thread.currentThread().getName() + "  main 线程 over");

    }
}
        JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
        System.out.println("main over");
  • 如果子线程执行,进程不会停止。

1.4.2,实现Runanble接口:

  • 定义子类,实现Runnable接口。
  • 类中重写Runnable接口中的run方法。
  • 通过Thread类含参构造器创建线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  • 调用Thread类的start方法:开启线程, 调用Runnable子类接口的run方法。
public class ThreadDemo {

    public static void main(String[] args) {
        //方式1:Thread子类,启动
        MyThread thread1 = new MyThread();
        thread1.start();

        //方式2:Runable方式(推荐的方式)
        //优势:可以实现多继承,比如继承BaseDao,然后再实现Runnable
        MyTask task = new MyTask();
        Thread thread2 = new Thread(task);
        thread2.start();

        //方式3:匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() +  "\t" +  i);
                }
            }
        }).start();
    }

}

//方式1
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() +  "\t" +  i);
        }
    }
}

//方式2
//优势:可以实现多继承,比如继承BaseDao,然后再实现Runnable
class MyTask implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() +  "\t" +  i);
        }
    }
}

1.4.3,Thread 常见方法

  • 构造函数

    • Thread(): 创建新的Thread对象
    • Thread(String threadname): 创建线程并指定线程实例名
    • Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法
    • Thread(Runnable target, String name): 创建新的Thread对象
  • void start(): 启动线程,并执行对象的run()方法

  • run(): 线程在被调度时执行的操作

  • String getName(): 返回线程的名称

  • **void setName(String name)😗*设置该线程名称

  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

  • static void yield(): 线程让步

    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程,忽略此方法
  • join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止

  • static void sleep(long millis): (指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。

  • stop(): 强制线程生命期结束,不推荐使用

  • boolean isAlive(): 返回boolean,判断线程是否还活着

1.4.4,sleep()方法

指定线程休眠的时间,单位毫秒,让出cpu时间片,其他线程可以抢占时间片

public class MyTask implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                //Thread.currentThread().getName():得到线程的名字
                System.out.println(Thread.currentThread().getName() + "\t" + i);
                try {
                    Thread.sleep(1000);
                    Thread.yield();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }
}

1.4.5,线程优先级

  • 线程的优先级等级

    • MAX_PRIORITY: 10
    • MIN _PRIORITY: 1
    • NORM_PRIORITY: 5
    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        Thread thread1 = new Thread(myTask, "t1");
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread1.start();

        Thread thread2 = new Thread(myTask, "t2");
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.start();
    }

1.4.6,守护线程

  • 其他线程都执行结束,守护线程自动结束
  • 守护启动子线程,也是守护线程
  • 守护线程的语法thread.(*setDaemon(true)*设置守护线程
public class Test2 {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
//        myThread1.setDaemon(true);
        myThread1.start(); //守护线程, gc线程,jvm线程结束gc会自动结束
        JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
        System.out.println("main over");
    }

}

class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("a");
        }
    }
}

1.4.7,线程合并

image-20230731101929769

案例:

public class Test1 {

    /**
     * CountDownLatch:可以实现相同的效果
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();

        try {
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Thread.currentThread().getName() +  "  main orver");

    }

}

image-20230731102014846

1.4.8,线程退出

  • stop():不推荐,线程退出方式粗暴,不管线程正在执行的任务,直接退出,可能丢失数据。

    image-20230731102216532

    public class Test1 {
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyTask());
            t1.start();
    
            Scanner in = new Scanner(System.in);
            System.out.println("输入1/0:0表示退出");
            int i = in.nextInt(); ///主线程进入IO阻塞
    
            if (i == 0) {
                t1.stop();
            }
    
            System.out.println("main over");
        }
    
    
        static class MyTask implements Runnable {
    
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
    
  • 中断信号interrupt

    image-20230731102257699

    • interrupt():发送中断信号(true)

      • 如果线程在阻塞状态,比如sleep(),join(),wait(),这时接收到中断信号会抛出一个异常InterruptException,同时中断信号清除(false)
      • 只是发送信号,不会对线程产生影响
    • **static interrupted():**得到中断信号(true),然后把中断信号设置成false

    • **isInterrupted():**得到中断信号,不会清除中断信号

    public class Test1 {
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyTask());
            t1.start();
    
            JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
            t1.interrupt(); //发送中断信号给t1
    
            System.out.println("main over");
        }
    
        static class MyTask implements Runnable {
    
            @Override
            public void run() {
                while (true) {
                    System.out.println("a");
    
                    try {
                        Thread.sleep(500000);
                    } catch (InterruptedException e) {
                        System.out.println("bbbbbbbbbbbbbbb");
                        e.printStackTrace();
                        Thread.currentThread().interrupt(); //再次发送中断信号,中断信号发给阻塞线程,抛出Interrupt异常,中断信号清除
    //                    throw new RuntimeException(e);
                    }
    
                    //得到中断信号,优雅的退出
                    if (Thread.interrupted()) {
                        break;
                    }
                }
            }
        }
    }
    

📢 1.5,线程的生命周期

1.5.1,线程的调度与时间片

​ 由于 CPU 的计算频率非常高,每秒计算数十亿次,于是,可以将 CPU 的时间从毫秒的维度进行分段,每一小段叫做一个 CPU 时间片。不同的操作系统、不同的处理器,线程的 CPU 时间片长度都不同。假定操作系统的线程一个时间片的时间长度为 20 毫秒(比如 Windows XP),在一个 2GHz 的 CPU 上,那么一个时间片可以进行计算的次数是: 20 亿/(1000/20) =4 千万次,也就是说,一个时间片内的计算量是非常巨大的。 目前操作系统中主流的线程调度方式大都是:基于 CPU 时间片方式进行线程调度。线程只有得到 CPU 时间片,才能执行指令,处于执行状态;没有得到时间片的线程,处于就绪状态,等待系统分配下一个 CPU 时间片。由于时间片非常短,在各个线程之间快速地切换,表现出来特征是很多个线程在“同时执行”或者“并发执行”。线程的调度模型,目前主要分为两种调度模型:分时调度模型、抢占式调度模型。

(1)分时调度模型——系统平均分配 CPU 的时间片,所有线程轮流占用 CPU。分时调度模型在时间片调度的分配上,所有线程人人平等。

下图就是一个分时调度的简单例子:三个线程,轮流得到 CPU 时间片;一个线程执行时,另外两个线程处于就绪状态

image-20230731102854819

(2)抢占式调度模型——系统按照线程优先级分配 CPU 时间片。优先级高的线程,优先分配 CPU 时间片;如果所有的就绪线程的优先级相同,那么会随机选择一个;优先级高的线程获取的 CPU 时间片相对多一些。 由于目前大部分操作系统都是使用抢占式调度模型进行线程调度。 Java 的线程管理和调度是委托给了操作系统完成的,与之相对应, Java 的线程调度也是使用抢占式调度模型

1.5.2,线程状态

操作系统,线程的状态

image-20230731103051812

java的线程状态

得到java的线程状态

public Thread.State getState(); //返回当前线程的执行状态,一个枚举类型值

Thread.State是一个内部枚举类,定义了6个枚举常量,分别代表Java线程的6种抓过你太,具体如下:

public static enum State {
     NEW, //新建
     RUNNABLE, //可执行:包含操作系统的就绪、运行两种状态
     BLOCKED, //阻塞  -> 操作系统线程中的阻塞
     WAITING, //等待  -> 操作系统线程中的阻塞
     TIMED_WAITING, //计时等待 -> 操作系统线程中的阻塞
     TERMINATED; //终止
 }

Thread.State定义的6中状态中,有四种是比较常见的状态,他们是:NEW状态,RUNNABLE状态,TERMINATED状态,TIMED_WAITING状态。

接下来,将线程的6种状态以及各种状态的进入条件,做个总结:

  1. NEW状态:通过 new Thread(...)已经创建线程,但尚未调用start()启动线程,该线程处于NEW(新建)状态。虽然前面介绍了4种创建线程,但是其中的其他三种方式,本质上都是通过new Thread()创建的线程,仅仅是创建了不同的target执行目标示例(如Runnable实例)。

  2. RUNNABLE状态:Java 把就绪(Ready)和 执行(Running)两种状态合并为一种状态:可执行(RUNNABLE)状态(或者可运行状态)。调用了线程的start()实例方法后,线程就处于就绪状态了;此线程获取到CPU时间片后,开始执行run()方法种的业务代码,线程处于执行状态。

    image-20230731142105785

    • 就绪状态:就绪状态仅仅表示线程具备运行资格,如果没有被操作系统的调度程序挑选中,线程就永远是就绪状态;当前线程进入就绪状态的条件,大致包括以下几种:

      • 调用线程的start()方法,此线程进入就绪状态。
      • 当前线程的执行时间片用完。
      • 线程睡眠(sleep)操作结束。
      • 对其他线程合入(join)操作结束。
      • 等待用户输入结束。
      • 线程争抢到对象锁(Object Monitor)。
      • 当前线程调用了yield()方法,让出CPU执行权限。
    • 执行状态:线程调度程序从就绪状态的线程中选择一个线程,作为当前线程时线程所处的状态。这也是线程进入执行状态的唯一方式。

    • BLOCKED 状态: 处于阻塞(BLOCKED)状态的线程并不会占用CPU资源,以下情况会让线程进入阻塞状态:

      1. 线程等待获取锁 :等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。
      2. IO 阻塞:线程发起了一个阻塞式 IO 操作后,如果不具备 IO 操作的条件,线程会进入阻塞状态。 IO 包括磁盘 IO、 网络 IO 等。 IO 阻塞的一个简单例子:线程等待用户输入内容后继续执行。
    • WAITING 状态:处于 WAITING(无限期等待)状态的线程不会被分配 CPU 时间片,需要被其他线程显式地唤醒,才会进入就绪状态。线程调用以下 3 种方法,会让自己进入无限等待状态:

      1. Object.wait() 方法,对应的唤醒方式为: Object.notify() / Object.notifyAll()。
      2. Thread.join() 方法,对应的唤醒方式为:被合入的线程执行完毕。
      3. LockSupport.park() 方法,对应的唤醒方式为: LockSupport.unpark(Thread)。
    • TIMED_WAITING 状态:处于 TIMED_WAITING(限时等待)状态的线程不会被分配 CPU 时间片,如果指定时间之内没有被唤醒,限时等待的线程会被系统自动唤醒,进入就绪状态。以下 3 个方法会让线程进入限时等待状态:

      • Thread.sleep(time) 方法,对应的唤醒方式为: sleep 睡眠时间结束。
      • Object.wait(time) 方 法 , 对 应 的 唤 醒 方 式 为 : 调 用 Object.notify() /Object.notifyAll()去主动唤醒,或者限时结束。
      • LockSupport.parkNanos(time)/parkUntil(time) 方法,对应的唤醒方式为:线程调用配套的 LockSupport.unpark(Thread)方法结束,或者线程停止(park)时限结束。

      进入 BLOCKED 状态、 WAITING 状态、 TIMED_WAITING 状态的线程都会让出 CPU 的使用权;另外,等待或者阻塞状态的线程被唤醒后,进入 Ready 状态,需要重新获取时间片才能接着运行。

    • TERMINATED 状态:线程结束任务之后,将会正常进入 TERMINATED(死亡)状态;或者说在线程执行过程中发生了异常(而没有被处理),也会导致线程进入死亡状态。

1.5.3,多线程自增 i++ 和线程执行原理

4个线程自增一个堆(共享的)里的对象的值

public class Test {

    private int i =0;

    public void f1() {
        i++;
    }
}

i++的反编译结果

javap -c Test.class

image-20230801211556153

image-20230801211859089

线程产生时 ,为每个线程的一个虚拟机栈分配1M内存

-Xss128K:指定没有线程的栈大小是128K

自增类:

public class Plus {

    private int amount = 0;

    public void selfPlus() {
            amount ++;
    }

    public int getAmount() {
        return amount;
    }

}

自增任务类

public class PlusTask implements Runnable {

    private Plus plus;

    public PlusTask() {
    }

    public PlusTask(Plus plus) {
        this.plus = plus;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100000000; i++) {
            plus.selfPlus();
        }
    }

}

测试

    public static void main(String[] args) throws InterruptedException {
        Plus plus = new Plus();
        PlusTask plusTask = new PlusTask(plus);

        Thread t1 = new Thread(plusTask);
        Thread t2 = new Thread(plusTask);
        Thread t3 = new Thread(plusTask);
        Thread t4 = new Thread(plusTask);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        System.out.println("实际的值 = " + plus.getAmount());

    }

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

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

相关文章

Nginx 15分钟入门

1、反向代理和负载均衡 Nginx 反向代理 负载均衡 网站初期用户量较少的时候&#xff0c;一台服务器就够用&#xff0c;但是当大量用户注册&#xff0c;那么显然一台机器就不够了。如下图&#xff0c;我们把同一个项目部署在3台服务器上。那么问题又来了&#xff0c;用户A的请…

连锁门店新零售管理系统服务商,提供新零售商城一体化解决方案|亿发

新零售时代&#xff0c;客户需求和购物方式正在发生翻天覆地的变化&#xff0c;数字化运营服务成为连锁门店增强竞争力的有效工具。那么&#xff0c;我们该如何借助数字化力量&#xff0c;升级连锁门店的新零售运营服务&#xff0c;迎接未来的商业挑战呢&#xff1f;一、智慧新…

emWin - BMP图片显示

BmpCvt.exe 用途 利用BMP图片&#xff0c;进行GUI显示&#xff1b;ICON等图标都是小BMP图片&#xff0c;核心是将BMP图片&#xff0c;转成emWin支持的方式&#xff0c;最终显示到TFT屏上 使用BmpCvt.exe工具&#xff0c;将各个图片转成相应的C文件. emWin有关的工具&#xff…

P4780 Phi的反函数

题目 思路 φ(x)n 当指数均为1时n最小 证明&#xff1a;容斥原理 代码 #include<bits/stdc.h> using namespace std; #define int long long const int maxn1e9; int ansINT_MAX,n; bool f; map<int,bool> mp; bool is_prime(int n){if(n<1) return false;fo…

Web功能测试之表单、搜索测试

初入职场接触功能测试老是碰到以下情况不知道怎么写测试用例&#xff1a; 一个界面很多搜索条件怎么写用例&#xff1f; 下拉框测试如何考虑测试点&#xff1f; 上传要考虑哪些验证点&#xff1f;...... 所以这篇主要是整理关于web测试之表单、搜索测试的相关要点。 一、表…

个性定义轻松掌控,更适合PC玩家的游戏鼠标,雷柏VT350S体验

喜欢玩PC游戏的玩家都知道&#xff0c;一款好的鼠标可以在游戏中更加游刃有余&#xff0c;甚至扭转乾坤。但是&#xff0c;有线鼠标总是让人觉得不够灵活&#xff0c;无线鼠标又担心延迟和续航。那么&#xff0c;有没有一款无线鼠标既能满足游戏需求&#xff0c;操作又能随心所…

话费充值系话费直充系统源码支付快充慢充系统运营商接口

话费充值系话费直充系统源码支付快充慢充系统运营商接口

小程序学习(五):WXSS模板语法

1.什么是WXSS WXSS是一套样式语言,用于美化WXML的组件样式,类似于网页开发中的CSS 2.WXSS和CSS的关系 WXSS模板样式-rpx 3.什么是rpx尺寸单位 4.rpx的实现原理 5.rpx与px之间的单位换算* WXSS模板样式-样式导入 6.什么是样式导入 使用WXSS提供的import语法,可以导入外联的样式…

Spring-BeanFactory

Spring Spring是整个Java体系最核心的框架&#xff0c;没有之一。 核心类图结构 ApplicationContext ApplicationEventPublisher&#xff1a;提供了一种机制&#xff0c;用于通知应用程序中感兴趣的部分有关其执行过程中发生的特定事件。ListableBeanFactory&#xff1a;是S…

P3855 [TJOI2008] Binary Land(BFS)(内附封面)

[TJOI2008] Binary Land 题目背景 Binary Land是一款任天堂红白机上的经典游戏&#xff0c;讲述的是两只相爱的企鹅Gurin和Malon的故事。两只企鹅在一个封闭的迷宫中&#xff0c;你可以控制他们向上下左右四个方向移动。但是他们的移动有一个奇怪的规则&#xff0c;即如果你按…

cloudstack之basic network

本章则主要尝试basic network模式的使用。 基础环境搭建见cloudstack测试环境搭建 1、概念介绍 cloudstack的网络模式主要分为两种&#xff1a; basic network&#xff1a;一个zone中仅有一个guest network来承载客户虚拟机的流量&#xff0c;可以理解为一个简单的二层网络。…

【EI/SCOPUS征稿】2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023)

2023年算法、图像处理与机器视觉国际学术会议&#xff08;AIPMV2023&#xff09; 2023 International Conference on Algorithm, Image Processing and Machine Vision&#xff08;AIPMV2023&#xff09; 2023年算法、图像处理与机器视觉国际学术会议&#xff08;AIPMV2023&am…

Django学习记录:使用ORM操作MySQL数据库并完成数据的增删改查

Django学习记录&#xff1a;使用ORM操作MySQL数据库并完成数据的增删改查 数据库操作 MySQL数据库pymysql Django开发操作数据库更简单&#xff0c;内部提供了ORM框架。 安装第三方模块 pip install mysqlclientORM可以做的事&#xff1a; 1、创建、修改、删除数据库中的…

maven如何打包你会吗?

1.新建一个maven项目&#xff0c;在main/java中建立Main类 public class Main {public static void main(String[] args) {System.out.println("hello java ...");} } 2.添加依赖&#xff0c;使其成为可执行包 <build><plugins><!--打包成为可执行包-…

uniapp app端 echarts 设置tooltip的formatter不生效问题以及解决办法

需求一&#xff1a; y轴数据处理不同数据增加不同单位 需求二&#xff1a; 自定义图表悬浮显示的内容 需求一&#xff1a;实现方式 在yAxis里面添加formatter yAxis: [{//y轴显示value的设置axisLabel: {show: true,formatter (value, index) > {var valueif (value > 1…

Uboot实现PSCI

快速链接: . &#x1f449;&#x1f449;&#x1f449; 【目录】ARM/TEE/ATF/SOC微信群问题记录 &#x1f448;&#x1f448;&#x1f448; 付费专栏-付费课程 【购买须知】: 问 &#xff1a; 8核的A72 多核启动 目前用的spin-table的方式&#xff0c;想尝试一下psci方式&…

Redis事务、管道

一.Redis事务 1.概念 可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行化执行而不会被其它命令插入&#xff0c;不许加塞 2.Redis事务与数据库事物的区别 3.常用命令 4.事务执行情况 正常执行 即整个过程…

全国高校招投标信息在哪里看?

很多投标人在查询招标信息的时候常常没有找到合适的&#xff0c;但是现在网上查询投标信息的网站是很多的。而学校招标信息获取的渠道是比较少的&#xff0c;企业的反而更多一些&#xff0c;那么我们能在那些渠道获取这些信息&#xff1f; 1.教育部网站 教育部提供了招标信息…

LeetCode 周赛上分之旅 # 36 KMP 字符串匹配殊途同归

⭐️ 本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架&#xff0c;你的思考越抽象&#xff0c;它能覆盖的问题域就越广&#xff0c;理解难度…