【JavaEE】多线程(初阶)

news2024/11/19 2:30:27

目录

Thread

线程的创建

线程的常见属性

中断线程

等待线程 

休眠线程

线程的状态

多线程相比于单线程的优势


Thread

在Java中操作多线程,最常用的类就是Thread。

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
Thread是java. lang下面的类,所以不需要import别的包。
每个Thread 的对象就对应到系统中的一个线程(也就是一个PCB)

线程的创建

1. 继承Thread,重写 run 方法

class MyThread extends Thread{
    @Override
    public void run() {
        while (true)
        {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();         //多线程
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 上述代码中,thread.start 表示创建一个新的线程,也就是调用操作系统的API,通过操作系统内核创建新线程的PCB,同时把要执行的指令交给这个PCB(线程是PCB描述的);新的线程负责运行 run() 方法,当PCB被调度到 CPU 上执行的时候,也就到了线程 run 方法中的代码了;

需要注意的是:start 并没有调用 run 方法,只是创建了新的线程,由新的线程来执行 run ,当 run 方法执行完了之后,新的这个线程就会自动销毁。要注意此处 start 和 run 的区别,start 是真正创建了一个线程,每一个线程都是一个独立的执行流,而run只是描述了线程要干的活;如果是在main 中直接调用 run 方法,那此时就没有创建新线程,而只有 main 一个线程在执行。

同时 new Thread 对象操作是不创建线程的,在调用 start 才是创建线程的时候。

在操作系统中,操作系统调度线程的时候,是 "抢占式执行" ,具体哪个线程先执行,哪个线程后执行,都是取决于操作系统调度器的具体实现策略。

对于线程安全问题,主要原因也就是这里的 "抢占式执行" "随即调度"

在上述代码中,就包含有两个线程,一个是主线程 main,还有一个是 thread;当我们运行上诉代码的时候,通过jdk自带的工具 jconsole 来查看当前的程序中包含有几个线程

 从中我们看出,是包含有主线程 main 和 创建的线程 thread 的。其他的线程便是在这个进程中其他的线程,包括JVM自带的线程等。

当我们运行的时候:

 运行结果也说明了,两个线程是并发运行,对于线程 main 和 线程 thread 谁先执行,后执行,我们也是不得而知的。

2. 实现 Runnable 接口

class MyRunnable implements Runnable{
    //Runnable作用:是描述一个 “ 要执行的任务 ” ,run方法就是任务的执行细节
    @Override
    public void run() {
        System.out.println("hello thread");
    }

}

public class ThreadDemo2 {
    public static void main(String[] args) {
        //这只是描述了一个任务
        Runnable runnable = new MyRunnable();
        //把任务交给线程来处理
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

 3. 使用匿名内部类,继承 thread 

这种写法本质上和第一种是一样的。

1. 创建一个 Thread 的子类,子类没有名字,所以是"匿名"

2. 创建了子类的实例,并且让 thread 引用指向该实例

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        };
        thread.start();
    }
}

 4. 使用匿名内部类,实现 Runnable

这种写法本质上和2是一样的

此处是创建了一个类,实现 Runnable ,同时创建了类的实例,并且传给了 Thread 的构造方法

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        });
        thread.start();
    }
}

5. 使用 Lambda 表达式

这也是我们最常用的方式 ,把要执行的任务用 lambda 表达式来描述,直接把 lambda 传给 Thread 构造方法。

(函数参数)->{  函数体  }

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println("hello thread");
        });
        thread.start();
    }
}

 所以说,线程常见的构造方法也就是以下几种:

Thread()
创建线程对象
Thread(Runnable target)
使用 Runnable 对象创建线程对象
Thread(String name)
创建线程对象,并命名
Thread(Runnable target, String name)
使用 Runnable 对象创建线程对象,并命名

一般线程的默认名就是 thread-0 这类的,例如刚刚看的线程名就是 Thread-0

线程的常见属性

属性获取方法
IDgetId()
名称 getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断
isInterrupted()

1. ID是线程的唯一标识,不同线程之间不会重复。

2. 名称表示构造方法中起的名字,一般系统会以thread-n的方式命名。

3. 状态表示线程所处的情况,后序会讲解。

4. 优先级高的线程理论上是会更容易被调用

5. 后台线程(守护线程):不会阻止进程结束,后台线程工作没做完,进程也是可以结束的;前台进程,是会阻止进程结束的,前台进程的工作没做完,进程是结束不了的。 

  需要注意的是:JVM会在一个进程中的所有非后台线程结束后,才会结束执行。

  代码里手动创建的线程,默认都是前台线程,包括 main 主线程,其他 jvm 自带的线程一般都是后台的,也可以手动 setDaemon 将其设置为后台线程。

6. 是否存活,就可以理解为 run 方法是否运行结束了。

在调用 start 之前,调用该方法结果为false,在执行结束后,结果也为false,执行过程中为true。因为在内核中线程的 run 执行完之后,线程就销毁了,PCB也随之释放。但thread这个对象还是存在的,不一定被释放了。

7. 中断下文会进行描述。

Thread.currentThread(); 表示获取当前线程。比如在thread中被调用就是获取到 thread线程。是一个静态方法,类似于 this 。

public class ThreadDemo17 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "还在执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "执行结束");
        });

        System.out.println("name: " + Thread.currentThread().getName());
        System.out.println("id: " + Thread.currentThread().getId());
        System.out.println("state: " + Thread.currentThread().getState());
        System.out.println("priority: " + Thread.currentThread().getPriority());
        System.out.println("Daemon: " + Thread.currentThread().isDaemon());
        System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().isAlive());
        System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().isInterrupted());
        System.out.println(thread.getName() + " " + thread.isAlive());

        thread.start();        //此时线程thread才真正创建

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(thread.getName() + " " + thread.isAlive());
    }
}

 从执行结果来看,也是可以看出两个线程之间的并发执行,和对应属性的变化。(添加 sleep 方法是为了调整好执行顺序来查看执行状态)

中断线程

要注意:线程中断,并不是让线程立即就停止,而是通知线程应该停止了。而线程是否真的停止了,取决于具体的代码实现。

所以说,会有三种不同的情况:

1. 通知线程中断,线程就立即中断了;

2. 通知线程中断,线程可能要过一会,等执行完某个语句,再进行中断;

3. 通知线程中断,线程不予理会,继续执行;

1.使用标志位来控制线程是否要停止 

public class ThreadDemo7 {
    private static  boolean flag = true;
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while(flag){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello");
            }
        });
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 在主线程里就可以随时通过 flag 变量的取值,来操作 t 线程是否结束
        flag = false;

    }
}

 正如执行结果所示,这段代码是通过修改变量 flag 的值来控制线程的中断,因此,线程是否中断,什么时候中断,是取决于线程内部代码实现的。但这种自定义变量来控制线程中断,有时候没办法及时响应,因为有些情况 sleep 的休眠时间过长。

2.使用Thread自带的标志位,进行判定 

thread.interrupt();

就表示通知 thread 线程要中断了,在main线程中调用,就相当于 main 通知 thread 应该中断了。

public class ThreadDemo8 {
    public static void main(String[] args){
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();          
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
    }
}

 !Thread.currentThread().isInterrupted()

当线程被通知中断的时候,这条语句输出为false;当线程没有被通知中断的时候,语句输出为 true。

从执行结果中可以看出, 线程 thread 输出三次之后,主线程 main 调用 thread.interrupt() 使线程thread收到中断信号,要注意,此时线程 thread 会发生两件事:

1.改变线程内部标志位,也就是 Thread.currentThread().isInterrupted() 变为 true ;

2.如果线程在 sleep ,就会触发异常,也就执行e.printStackTrace();语句,把 thread 线程唤醒,让线程 thread 从 sleep 中提前返回。正如执行结果所示。

但是此时,线程 thread 还是继续循环执行,这是因为,在sleep被唤醒的时候,还会做另一件事:清空标志位,也就是把刚才设置标志位为 true,现在再设置为 false。因此thread也就继续循环输出了;

这样就再次说明了一点:线程中断,不是真的中断,而只是通知线程应该中断了,具体会不会中断,还得看具体的代码实现。

 所以,这就是线程中断的第一种情况,通知 thread 线程中断了,但是线程忽略了中断请求。

下面介绍第二种,通知线程中断,线程立即中断。

public class ThreadDemo8 {
    public static void main(String[] args){
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();   
                    break;                //加上break: 线程t立即响应你的中断请求       
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
    }
}

第三种,线程收到中断请求后,过一会再进行中断。

public class ThreadDemo8 {
    public static void main(String[] args){
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();  
                    try {                           //理解成稍后再进行中断
                        Thread.sleep(2000);         //这里也可以是具体的实现代码
                    } catch (InterruptedException ex) {
                        throw new RuntimeException(ex);
                    }
                    break;

                }
            }
        });
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
    }
}

因此,interrupt 只是告诉线程应该中断了,但线程并不是真的就中断了,具体是要取决于代码实现的。

这里还有另一种情况,就是如果线程 thread 不在 sleep ,此时外部调用 interrupt 通知线程 thread 中断,标志位状态变为 true,这个时候线程 thread 还是会正常执行的,因为 interrupt 方法只是通知线程中断,但并不是立刻停止执行,所以它还是会继续往下执行,直到执行到 sleep,这个时候抛出异常,如果此时标志状态为 true ,表示的是这个线程处于中断状态,就无法处理这个异常,所以会把中断标志重置为 false ,然后去处理异常。

总之,还是那句话,调用 interrup 之后,线程不是立马就中断了,而是要看具体的代码实现。

等待线程 

thread.join();

线程是一个随机调度的过程,等待线程,本质上,就是在控制两个线程的结束顺序

public class ThreadDemo9 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();


        System.out.println("join 之前");

        try {
            thread.join();                  //等待thread执行完
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("join 之后");

    }
}

上述代码中,在 main 线程中调用 thread.join ,此时 main 线程会进入阻塞等待状态,本质上就是 main 线程等待 thread 线程执行完了之后再继续执行。

在执行完 thread.start() 后,thread 线程和 main 线程就开始并发执行。main线程执行到 thread.join() 的时候,就开始阻塞了,一直阻塞到 thread 线程执行结束, main 线程才会从 join 中恢复回来,才能继续往下执行。thread 线程肯定是比 main 先结束的,从执行结果中也可以看出。

如果是在执行 join 的时候,thread 已经结束了,join 就不会阻塞,而是立即返回,继续向下执行。 

对于等待线程,死等是不常见的,这会影响开发效率,所以一般都会设置最大时间数。 

public void join()
等待线程结束
public void join(long millis)
等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)
等待线程结束,最多等 millis 毫秒+nanos纳秒

休眠线程

休眠线程,本质上就是让这个线程不参与调度了。(不去 CPU 上执行)也可以理解为进入阻塞状态。

public static void sleep(long millis)throws InterruptedException
休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException
休眠当前线程 millis毫秒+nanos纳秒

 在操作系统内核中,会有就绪队列和阻塞队列,我们前面也讲过 PCB 是使用链表来组织的,但也并不具体,实际的情况可能不是一个简单的链表,而是以链表为核心的数据结构。

在就绪队列中,PCB都是 "随叫随到" 的,处于就绪状态,而操作系统每次需要调度一个线程去执行的时候,就从就绪队列中进行挑选。

当线程A调用 sleep ,A就会进入休眠状态,也就把线程A从就绪队列中转移至阻塞队列中,在阻塞队列中的PCB,都是 "阻塞状态" ,暂时不参加CPU的调度执行。 

一旦线程进入阻塞状态,对应的PCB就进入阻塞队列了,此时就暂时无法参与调度了。

比如线程A sleep(1000) ,对应的PCB就要在阻塞队列中待1000ms。

当这个PCB回到就绪队列中,会被立即执行吗?

虽然是 sleep(1000) ,但是实际上考虑到调度的开销,对应的线程是无法在唤醒后立即执行的,实际上的时间间隔大概率是要大于 1000ms 的。

线程的状态

状态是针对当前的线程调度的情况来描述的。

1. NEW:创建了 Thread 对象,但是还没有调用 start (内核里还没有对应的PCB)

2. TERMINATED:表示内核里的PCB已经执行完毕了,但是 Thread 对象还在。

3. RUNNABLE:可运行的。包括两种状态:正在CPU上运行的,在就绪队列里,随时可以去CPU上运行的。

4. WAITING:wait / join(后面进行讲解)

5. TIME_WAITING:线程处于 sleep 中,当 sleep 时间到了,就解除阻塞。

6.BLOCK:由于加锁操作的阻塞,获取到锁的时候,解除阻塞(后面进行讲解)

4,5,6 是趋于不同原因的阻塞状态,表示线程PCB正在阻塞队列中,后序会一一介绍。

TERMINATED:一旦内核里的线程PCB销毁了,此时代码中的 thread 对象也就没意义了,内核的线程释放的时候,无法保证代码中 thread 对象也立即释放,因此就需要 TERMINATED 这个特定的状态,来把 thread 这个对象标识为 "无效"  此时这个对象也是不能再次 start 的,一个线程只能 start 一次。虽然此时的对象无法通过多线程来做一些事,但对象还存在,还是可以调用对象的一些方法属性来进行使用。

public class ThreadDemo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                try {                               //加上这一段sleep之后,在后续打印中,具体看到的是RUNNABLE还是TIME_WAITING就不一定
                    Thread.sleep(10);           //取决于当前的 t 线程是运行到哪一个环节了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动之前,获取 t 的状态
        System.out.println("start之前: " + t.getState());
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("t 执行中的状态:" + t.getState());
        }

        t.join();
        //线程执行完毕之后,获取 t 的状态
        System.out.println("t 结束之后:" + t.getState());
    }
}

 

 从上述代码的执行结果可以看出线程对应的状态,正如上文所讲,在 start 之前,t 线程的状态是NEW;

在 t 线程执行完后,线程销毁,但 t 对象还在,此时 t 线程的状态为 TERMINATED;

在线程 t 的执行过程中,t 的状态有两种,sleep 状态下为 TIMED_WAITING,其他情况下为RUNNABLE状态。但是相比于线程 t 在CPU上执行的时间来说,sleep(10)这个时间就太长了,因此从输出结果也可以看出,大部分的状态都在 TIMED_WAITING,如果想要两者之间的状态更均衡一些,就可以在 run 方法中引入更多的计算逻辑来增加时间消耗。

剩下的 WAITING 和 BLOCKED 后面再为大家解答~

多线程相比于单线程的优势

前面铺垫了那么多线程知识,我们现在也来感受一下多线程和单线程之间的执行速度差别。 

一般我们的程序分为:CPU密集 和 IO密集

CPU密集:包含了大量的加减乘除等运算;

IO密集:涉及到读写文件,读写控制台,读写网络等; 

为了减小误差,我们运算量稍微大一些,因为一般衡量执行时间的代码,让程序跑的久一些,也并不是坏事,因为线程本身调度是需要时间开销的,运算量越大,线程本身调度的时间开销相比之下就不那么明显了,也就变得可忽略不计,从而减小误差。 

public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        //假设当前有两个变量,需要把两个变量各自自增 100亿次:典型的cup密集型的场景
        //可以一个线程,先针对 a 的自增,然后再针对 b 自增
        //还可以两个线程,分别对 a 和 b 自增
        serial();
        concurrency();
    }

    //串行执行:一个线程来完成
    public static void serial(){
    //为了衡量执行速度,加上个计时的操作
    //currentTimeMillis 获取到当前系统的 ms 级时间戳
    long beg = System.currentTimeMillis();
    long a = 0;
    for (long i = 0; i < 100_0000_0000L; i++) {
            a++;
    }
    long b = 0;
    for(long i = 0;i < 100_0000_0000L;i++){
        b++;
    }
    long over = System.currentTimeMillis();
    System.out.println("执行时间:" + (over - beg)+"ms");
    }

    public static void concurrency() throws InterruptedException {
        //使用两个线程分别完成自增
        Thread t1 = new Thread(()->{
            long a = 0;
            for (long i = 0; i < 100_0000_0000L; i++) {
                a++;
            }
        });
        Thread t2 = new Thread(()->{
            long b = 0;
            for (long i = 0; i < 100_0000_0000L ; i++) {
                b++;
            }
        });

        long beg = System.currentTimeMillis();
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        long over = System.currentTimeMillis();
        System.out.println("执行时间:" + (over - beg)+"ms");

    }
}

 

 

 首先要注意,我们每一次执行的时间大概率都是不同的,只能说是相差不大,因为线程的随机调度。同时在获取最终时间的时候,应该在语句 t1.join 和 t2.join 后再获取,要确保 t1 和 t2 线程已经执行完。

从执行结果可以看出,多线程的情况下要比单线程的执行速度快很多,这也是因为多线程可以更充分的利用到多核心CPU资源。

所以说,多线程,在这种 CPU 密集型的任务中,就有着非常大的作用,可以充分利用多核 CPU 资源,从而加快程序的运行速度。

当然,多线程在 IO 密集型的任务中,也有着较大的作用。例如,在我们打开一些数据量比较大的游戏的时候,程序就需要进行一些耗时的 IO 操作,要加载大量的数据文件,涉及到大量的读取硬盘操作,阻塞了界面的相应,就会出现 "程序未响应" 的情况,这时候用多线程就可以有很好的改善,也就是一个线程负责 IO,一个线程负责相应用户的操作。

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

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

相关文章

一网打尽带环链表问题(手把着手带你理解,木有基础也能看懂!)

目录 0.前言 1.认识带环链表 2.带环链表OJ问题简述 3.判断是否为带环链表 4. 寻找入环节点&#xff08;法一&#xff1a;数学推理的魅力&#xff09; 5. 寻找入环节点&#xff08;暴力拆解带环链表的魄力&#xff09; 6.番外&#xff1a;判断是否为带环链表(fast和slow的…

树上的DP

ACACAC来的如此之快&#xff0c;让我以为还要再调试几个点(h)&#xff0c;本文涉及思路参考&#xff1a;参考文章 嗯,这个题和思路让我对跨父节点的树上链有了一个认识&#xff0c;我们来看一下&#xff1a; 如果说&#xff0c;我们要对这样的一条链进行dpdpdp操作&#xff0c…

[GXYCTF2019]BabysqliV3.0

发现我们随便上传一个以后返回的是 alert(Wrong pass); 密码错误 如果上传一个账号永真的话返回 Not this user! 没有这个用户&#xff0c;所以感觉可以使用盲注&#xff0c;对账号进行盲注 可是题目 ascii(substr((select database()),1,1))>1 也不对 最后才知道是…

电脑组装配件知识

目录 1.电脑硬件基础知识 1.1CPU 1.2内存 ​编辑 1.3硬盘 1.4主板 1.5显卡 ​编辑 1.6显示器 1.7电源 1.8机箱 2.电脑硬件搭配及选购 2.1硬件搭配原则 2.2怎样查询软件或游戏配置 2.3配件购买注意事项 2.4搭配一台普通办公电脑 3.电脑组装 1.电脑硬件基础知识 …

软件测试/测试开发 | 一文学会 Appium 环境配置

Appium 是一个开源的、跨平台的测试框架&#xff0c;可以用来测试 Native App、混合应用、移动 Web 应用&#xff08;H5 应用&#xff09;等&#xff0c;也是当下互联网企业实现移动自动化测试的重要工具。Appium 坚持的测试理念&#xff1a; •无需用户对 App 进行任何修改或…

OAuth2.0 开放平台认证授权开发套件 Authmore-Framework 1.0 发布

简介 | Intro 基于 OAuth2.0 协议的开放平台认证授权开发套件, 包含授权服务和开放平台 Docker 镜像&#xff0c;基于 Spring Boot Starter 的资源服务工具包和客户端&#xff08;第三方应用&#xff09;工具包 优点 | Advantages 简洁&#xff1a;专注核心功能 —— 社会化…

剑指offer----C语言版----第十六天----面试题22:链表中的倒数第k个节点

目录 1. 链表中倒数第 k 个节点 1.1 题目描述 1.2 思路一 1.3 思路二&#xff1a; 1.4 总结----代码的鲁棒性 1. 链表中倒数第 k 个节点 原题链接&#xff1a; 剑指 Offer 22. 链表中倒数第k个节点 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/l…

竹制品行业市场运行态势及未来前景分析

2023-2029年中国竹制品行业市场运行态势及未来前景分析报告报告编号&#xff1a;1691644免费目录下载&#xff1a;http://www.cninfo360.com/yjbg/qthy/qt/20230110/1691644.html本报告著作权归博研咨询所有&#xff0c;未经书面许可&#xff0c;任何组织和个人不得以任何形式复…

基于Power BI的终端消费会员客户价值RFM分析

一、原理&#xff1a;RFM分析 &#xff08;一&#xff09;概念 RFM模型衡量当前客户价值和客户潜在价值的重要工具和手段。通过一个客户的近期购买行为、购买的总体频率以及花了多少钱三个维度来描述该客户价值状况的客户分类模型。 &#xff08;二&#xff09;分析维度 R&a…

使用标量衍射理论和菲涅耳和弗劳恩霍夫衍射的衍射对波动光学进行建模(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 本文使用Matlab对波动光学进行建模。包括使用标量衍射理论和菲涅耳和弗劳恩霍夫衍射的衍射。 &#x1f4da;2 运行结果 &#…

北大硕士LeetCode算法专题课-栈、队列相关问题

算法面试相关专题&#xff1a; 北大硕士LeetCode算法专题课--链表相关问题_ 北大硕士LeetCode算法专题课-查找相关问题_ 北大硕士LeetCode算法专题课-字符串相关问题_ 北大硕士LeetCode算法专题课-数组相关问题_ _ 北大硕士LeetCode算法专题课-基础算法之排序_ 北…

四、Javaweb之Filter、Listener、Ajax、Vue

文章目录12. Filter 和 Listener12.1 Filter快速入门和执行流程12.2 Filter使用细节12.3 Filter案例&#xff1a;登录验证12.4 Listener13. AJAX13.1 AJAX快速入门13.2 AJAX案例13.3 Axios异步框架13.4 JSON13.5 JSON案例SelectAllServlet.javabrand.htmlAddServlet.javaaddBra…

某集团汽车配件电子图册性能分析案例(二)

背景 汽车配件电子图册系统是某汽车集团的重要业务系统。业务部门反映&#xff0c;汽车配件电子图册调用图纸时&#xff0c;出现访问慢现象。 某汽车集团总部已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量。本次分析重点针对汽车配件电子图…

JDBC简介及原理和使用介绍

JDBC简介及原理和使用介绍JDBC简介 jdbc概述 ​ Java数据库连接&#xff0c;&#xff08;Java Database Connectivity&#xff0c;简称JDBC&#xff09;是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口&#xff0c;提供了诸如查询和更新数据库中数据的方法。 …

WebGL-iTwin.js】实战篇(二):用nodejs代码详解iTwin.js中PhysicalObject生成方法

PhysicalObject 即真实存在的物理对象&#xff0c;比如&#xff1a;电脑、桌子等。在webgl中&#xff0c;我们人眼能看到的模型都是PhysicalObject&#xff0c;由多种几何图元类型构成&#xff0c;如&#xff1a;网格、实体、曲面、曲线&#xff0c;点云等。 其中带索引的多边…

XCTF:cat

打开是一个输入框&#xff0c;要求输入域名&#xff0c;尝试输入baidu.com进行测试 并无任何回显&#xff0c;测试下127.0.0.1本地地址 执行成功&#xff0c;并出现ping命令结果&#xff0c;这里很容易联想到命令注入&#xff0c;尝试进行命令拼接注入 但测试了常用的拼接字…

【5】变量和常量

一、什么是变量 首先我们要理解这么一个概念&#xff0c;在程序的运行过程中所有数据是保存在内存中的&#xff0c;我们代码中想使用这个数据的时候就要从内存中找&#xff0c;而变量的作用类似就是将内存地址保存&#xff0c;之后直接通过这个变量找内存中的数在Go语言中&…

JNI和Ndk开发

按照一下配置&#xff0c;基本能保证demo跑通 1、下载NDK&#xff0c;下载需要的版本 2、下载Cmake版本 3、项目结构&#xff1a;含C源码 4、编写JNI的加载类 public class YjkModel {static {System.loadLibrary("nativ"); //跟CMakeLists.txt 库名一致}public nat…

基于 APISIX 的服务网格方案 Amesh 积极开发中!

作者lingsamuel&#xff0c;API7.ai 云原生技术专家&#xff0c;Apache APISIX Committer。 在云原生快速发展的前提下&#xff0c;服务网格领域也开始逐渐火热。目前阶段&#xff0c;大家所熟知的服务网格解决方案很多&#xff0c;每种产品又各有其优势。因此在面对不同的行业…

python直接赋值、浅拷贝与深拷贝

本文主要参考这篇博文python浅拷贝与深拷贝 基本概念 首先了解python中的一些基本概念 变量&#xff1a;是一个系统表的元素&#xff0c;拥有指向对象的连接空间对象&#xff1a;被分配的一块内存&#xff0c;存储其所代表的值引用&#xff1a;是自动形成的从变量到对象的指…