JUC并发编程01——进程,线程(详解),并发和并行

news2024/11/25 16:44:27

目录

  • 1.进程和线程的概念及对比
    • 1.进程
      • 概述
    • 2.线程
    • 3.对比
  • 2.并行与并发
    • 1.并发
    • 2.并行
  • 3.线程详解
    • 3.1.创建和运行线程
      • 3.1.1.Thread
      • 3.1.2.Runnable结合Thread 创建线程
      • 3.1.3.Callable
    • 3.2线程方法
      • API
      • run start
      • sleep yield
      • join
      • interrupt
          • 打断线程
          • 打断 park
          • 终止模式
      • daemon
      • 不推荐使用的方法

1.进程和线程的概念及对比

1.进程

概述

进程:程序是静止的,进程实体的运行过程就是进程,是系统进行资源分配的基本单位

进程的特征:并发性、异步性、动态性、独立性、结构性

线程:线程是属于进程的,是一个基本的 CPU 执行单元,是程序执行流的最小单元。线程是进程中的一个实体,是系统独立调度的基本单位,线程本身不拥有系统资源,只拥有一点在运行中必不可少的资源,与同属一个进程的其他线程共享进程所拥有的全部资源

关系:一个进程可以包含多个线程,这就是多线程,比如看视频是进程,图画、声音、广告等就是多个线程

线程的作用:使多道程序更好的并发执行,提高资源利用率和系统吞吐量,增强操作系统的并发性能

并发并行:

  • 并行:在同一时刻,有多个指令在多个 CPU 上同时执行
  • 并发:在同一时刻,有多个指令在单个 CPU 上交替执行

同步异步:

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

2.线程

一个进程之内可以分为一到多个线程。一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。
Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

3.对比

线程进程对比:

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集

  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享

  • 进程间通信较为复杂

    同一台计算机的进程通信称为 IPC(Inter-process communication)

    • 信号量:信号量是一个计数器,用于多进程对共享数据的访问,解决同步相关的问题并避免竞争条件
    • 共享存储:多个进程可以访问同一块内存空间,需要使用信号量用来同步对共享存储的访问
    • 管道通信:管道是用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件 pipe 文件,该文件同一时间只允许一个进程访问,所以只支持半双工通信
      • 匿名管道(Pipes):用于具有亲缘关系的父子进程间或者兄弟进程之间的通信
      • 命名管道(Names Pipes):以磁盘文件的方式存在,可以实现本机任意两个进程通信,遵循 FIFO
    • 消息队列:内核中存储消息的链表,由消息队列标识符标识,能在不同进程之间提供全双工通信,对比管道:
      • 匿名管道存在于内存中的文件;命名管道存在于实际的磁盘介质或者文件系统;消息队列存放在内核中,只有在内核重启(操作系统重启)或者显示地删除一个消息队列时,该消息队列才被真正删除
      • 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收

    不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP

    • 套接字:与其它通信机制不同的是,可用于不同机器间的互相通信
  • 线程通信相对简单,因为线程之间共享进程内的内存,一个例子是多个线程可以访问同一个共享变量

    Java 中的通信机制:volatile、等待/通知机制、join 方式、InheritableThreadLocal、MappedByteBuffer

  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

2.并行与并发

1.并发

单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent
在这里插入图片描述

在这里插入图片描述

2.并行

多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
在这里插入图片描述
在这里插入图片描述
引用 Rob Pike 的一段描述:

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel)是同一时间动手做(doing)多件事情的能力

并发和并行经常会同时存在(线程数大于CPU核数)

  1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活
  2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的
  • 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分(参考后文的【阿姆达尔定律】)
  • 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
  1. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化

3.线程详解

3.1.创建和运行线程

3.1.1.Thread

Thread 创建线程方式:创建线程类,匿名内部类方式

  • start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行
  • 线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程
  • 建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完

Thread 构造器:

  • public Thread()
  • public Thread(String name)
// 创建线程对象
 Thread t = new Thread() {
	 public void run() {
	 // 要执行的任务
	 }
 };
	// 启动线程
	t.start();

在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args) {
    	//创建线程
        Thread t = new MyThread();
        //启动线程
        t.start();
       	for(int i = 0 ; i < 100 ; i++ ){
            System.out.println("main线程" + i)
        }
        // main线程输出放在上面 就变成有先后顺序了,因为是 main 线程驱动的子线程运行
    }
}
class MyThread extends Thread {
    @Override
    // run 方法内实现了要执行的任务
    public void run() {
        for(int i = 0 ; i < 100 ; i++ ) {
            System.out.println("子线程输出:"+i)
        }
    }
}

继承 Thread 类的优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承了 Thread 类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)

3.1.2.Runnable结合Thread 创建线程

Runnable 创建线程方式:创建线程类,匿名内部类方式

Thread 的构造器:

  • public Thread(Runnable target)
  • public Thread(Runnable target, String name)
public class ThreadDemo {
    public static void main(String[] args) {
        Runnable target = new MyRunnable();
        Thread t1 = new Thread(target,"1号线程");
		t1.start();
        Thread t2 = new Thread(target);//Thread-0
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }
}

Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable#run:

public class Thread implements Runnable {
    private Runnable target;
    
    public void run() {
        if (target != null) {
          	// 底层调用的是 Runnable 的 run 方法
            target.run();
        }
    }
}

Runnable 方式的优缺点:

  • 缺点:代码复杂一点。

  • 优点:

    1. 线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性

    2. 同一个线程任务对象可以被包装成多个线程对象

    3. 适合多个多个线程去共享同一个资源

    4. 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立

    5. 线程池可以放入实现 Runnable 或 Callable 线程任务对象

3.1.3.Callable

实现 Callable 接口:

  1. 定义一个线程任务类实现 Callable 接口,申明线程执行的结果类型
  2. 重写线程任务类的 call 方法,这个方法可以直接返回执行的结果
  3. 创建一个 Callable 的线程任务对象
  4. 把 Callable 的线程任务对象包装成一个未来任务对象
  5. 把未来任务对象包装成线程对象
  6. 调用线程的 start() 方法启动线程

public FutureTask(Callable<V> callable):未来任务对象,在线程执行完后得到线程的执行结果

  • FutureTask 就是 Runnable 对象,因为 Thread 类只能执行 Runnable 实例的任务对象,所以把 Callable 包装成未来任务对象
  • 线程池部分详解了 FutureTask 的源码

public V get():同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步

  • get() 线程会阻塞等待任务执行完成
  • run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值

优缺点:

  • 优点:同 Runnable,并且能得到线程执行的结果
  • 缺点:编码复杂
public class ThreadDemo {
    public static void main(String[] args) {
        Callable call = new MyCallable();
        FutureTask<String> task = new FutureTask<>(call);
        Thread t = new Thread(task);
        t.start();
        try {
            String s = task.get(); // 获取call方法返回的结果(正常/异常结果)
            System.out.println(s);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

public class MyCallable implements Callable<String> {
    @Override//重写线程任务类方法
    public String call() throws Exception {
        return Thread.currentThread().getName() + "->" + "Hello World";
    }
}

3.2线程方法

API

Thread 类 API:

方法说明
public void start()启动一个新线程,Java虚拟机调用此线程的 run 方法
public void run()线程启动后调用该方法
public void setName(String name)给当前线程取名字
public void getName()获取当前线程的名字
线程存在默认名称:子线程是 Thread-索引,主线程是 main
public static Thread currentThread()获取当前线程对象,代码在哪个线程中执行
public static void sleep(long time)让当前线程休眠多少毫秒再继续执行
Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争
public static native void yield()提示线程调度器让出当前线程对 CPU 的使用
public final int getPriority()返回此线程的优先级
public final void setPriority(int priority)更改此线程的优先级,常用 1 5 10
public void interrupt()中断这个线程,异常处理机制
public static boolean interrupted()判断当前线程是否被打断,清除打断标记
public boolean isInterrupted()判断当前线程是否被打断,不清除打断标记
public final void join()等待这个线程结束
public final void join(long millis)等待这个线程死亡 millis 毫秒,0 意味着永远等待
public final native boolean isAlive()线程是否存活(还没有运行完毕)
public final void setDaemon(boolean on)将此线程标记为守护线程或用户线程

run start

run:称为线程体,包含了要执行的这个线程的内容,方法运行结束,此线程随即终止。直接调用 run 是在主线程中执行了 run,没有启动新的线程,需要顺序执行

start:使用 start 是启动新的线程,此线程处于就绪(可运行)状态,通过新的线程间接执行 run 中的代码

说明:线程控制资源类

run() 方法中的异常不能抛出,只能 try/catch

  • 因为父类中没有抛出任何异常,子类不能比父类抛出更多的异常
  • 异常不能跨线程传播回 main() 中,因此必须在本地进行处理

sleep yield

sleep:

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • sleep() 方法的过程中,线程不会释放对象锁
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield:

  • 调用 yield 会让提示线程调度器让出当前线程对 CPU 的使用
  • 具体的实现依赖于操作系统的任务调度器
  • 会放弃 CPU 资源,锁资源不会释放

join

public final void join():等待这个线程结束

原理:调用者轮询检查线程 alive 状态,t1.join() 等价于:

public final synchronized void join(long millis) throws InterruptedException {
    // 调用者线程进入 thread 的 waitSet 等待, 直到当前线程运行结束
    while (isAlive()) {
        wait(0);
    }
}
  • join 方法是被 synchronized 修饰的,本质上是一个对象锁,其内部的 wait 方法调用也是释放锁的,但是释放的是当前的线程对象锁,而不是外面的锁

  • 当调用某个线程(t1)的 join 方法后,该线程(t1)抢占到 CPU 资源,就不再释放,直到线程执行完毕

线程同步:

  • join 实现线程同步,因为会阻塞等待另一个线程的结束,才能继续向下运行
    • 需要外部共享变量,不符合面向对象封装的思想
    • 必须等待线程结束,不能配合线程池使用
  • Future 实现(同步):get() 方法阻塞等待执行结果
    • main 线程接收结果
    • get 方法是让调用线程同步等待
public class Test {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        });
        t1.start();
        t1.join();//不等待线程执行结束,输出的10
        System.out.println(r);
    }
}

interrupt

打断线程

public void interrupt():打断这个线程,异常处理机制

public static boolean interrupted():判断当前线程是否被打断,打断返回 true,清除打断标记,连续调用两次一定返回 false

public boolean isInterrupted():判断当前线程是否被打断,不清除打断标记

打断的线程会发生上下文切换,操作系统会保存线程信息,抢占到 CPU 后会从中断的地方接着运行(打断不是停止)

  • sleep、wait、join 方法都会让线程进入阻塞状态,打断线程会清空打断状态(false)

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
        System.out.println(" 打断状态: {}" + t1.isInterrupted());// 打断状态: {}false
    }
    
  • 打断正常运行的线程:不会清空打断状态(true)

    public static void main(String[] args) throws Exception {
        Thread t2 = new Thread(()->{
            while(true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                if(interrupted) {
                    System.out.println(" 打断状态: {}" + interrupted);//打断状态: {}true
                    break;
                }
            }
        }, "t2");
        t2.start();
        Thread.sleep(500);
        t2.interrupt();
    }
    

打断 park

park 作用类似 sleep,打断 park 线程,不会清空打断状态(true)

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread(() -> {
        System.out.println("park...");
        LockSupport.park();
        System.out.println("unpark...");
        System.out.println("打断状态:" + Thread.currentThread().isInterrupted());//打断状态:true
    }, "t1");
    t1.start();
    Thread.sleep(2000);
    t1.interrupt();
}

如果打断标记已经是 true, 则 park 会失效

LockSupport.park();
System.out.println("unpark...");
LockSupport.park();//失效,不会阻塞
System.out.println("unpark...");//和上一个unpark同时执行

可以修改获取打断状态方法,使用 Thread.interrupted(),清除打断标记

LockSupport 类在 同步 → park-un 详解


终止模式

终止模式之两阶段终止模式:Two Phase Termination

目标:在一个线程 T1 中如何优雅终止线程 T2?优雅指的是给 T2 一个后置处理器

错误思想:

  • 使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止

两阶段终止模式图示:

打断线程可能在任何时间,所以需要考虑在任何时刻被打断的处理方法:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}
class TwoPhaseTermination {
    private Thread monitor;
    // 启动监控线程
    public void start() {
        monitor = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Thread thread = Thread.currentThread();
                    if (thread.isInterrupted()) {
                        System.out.println("后置处理");
                        break;
                    }
                    try {
                        Thread.sleep(1000);					// 睡眠
                        System.out.println("执行监控记录");	// 在此被打断不会异常
                    } catch (InterruptedException e) {		// 在睡眠期间被打断,进入异常处理的逻辑
                        e.printStackTrace();
                        // 重新设置打断标记,打断 sleep 会清除打断状态
                        thread.interrupt();
                    }
                }
            }
        });
        monitor.start();
    }
    // 停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

daemon

public final void setDaemon(boolean on):如果是 true ,将此线程标记为守护线程

线程启动前调用此方法:

Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println("running");
    }
};
// 设置该线程为守护线程
t.setDaemon(true);
t.start();

用户线程:平常创建的普通线程

守护线程:服务于用户线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。守护进程是脱离于终端并且在后台运行的进程,脱离终端是为了避免在执行的过程中的信息在终端上显示

说明:当运行的线程都是守护线程,Java 虚拟机将退出,因为普通线程执行完后,JVM 是守护线程,不会继续运行下去

常见的守护线程:

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

不推荐使用的方法

不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁:

  • public final void stop():停止线程运行

    废弃原因:方法粗暴,除非可能执行 finally 代码块以及释放 synchronized 外,线程将直接被终止,如果线程持有 JUC 的互斥锁可能导致锁来不及释放,造成其他线程永远等待的局面

  • public final void suspend()挂起(暂停)线程运行

    废弃原因:如果目标线程在暂停时对系统资源持有锁,则在目标线程恢复之前没有线程可以访问该资源,如果恢复目标线程的线程在调用 resume 之前会尝试访问此共享资源,则会导致死锁

  • public final void resume():恢复线程运行

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

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

相关文章

MATLAB中hilb函数用法

MATLAB中的hilb函数用于生成希尔伯特矩阵。 语法为: H hilb(n) 其中: n: 生成的希尔伯特矩阵的阶数 H: 生成的n阶希尔伯特矩阵 希尔伯特矩阵又称为希尔伯特运算矩阵,它是一种测试矩阵,元素H(i,j) 1/(ij-1),i和j表示矩阵的行号和列号。 例如: H hilb(7)会生成一个7阶…

SPI指数计算(Standardized Precipitation Index,标准化降水指数) 附完整MATLAB代码

SPI指数(Standardized Precipitation Index,标准化降水指数)是反映干湿状况的一个指标,主要计算步骤如下: 收集研究区域过去30年或以上时间尺度(一般选取30年)的月降水量资料。 对月降水量资料进行统计分析,拟合出最适合的概率分布函数。常用的有Pearson III 分布、Gamma分布…

【Docker】使用VS创建、运行、打包、部署.net core 6.0 webapi

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

【DDD】学习笔记-限界上下文对架构的影响

通信边界对架构的影响 限界上下文的通信边界会对系统的架构产生直接的影响&#xff0c;在此之前&#xff0c;我们需要理清几个和边界有关的概念。如前所述&#xff0c;我提出了限界上下文的通信边界的概念&#xff0c;并将其分为进程内通信与进程间通信两种方式。在 Toby Clem…

文生图提示词:城市景观

场景描述 --城市景观 Urban Landscapes 涵盖了多种城市景观元素&#xff0c;可以用于精确地表达 AI 生成图像中所需的城市环境。 Cityscape 城市景观 Downtown 市中心 Skyline 天际线 Skyscraper 摩天大楼 Street 街道 Avenue 大道 Boulevard 林荫大道 Plaza 广场 Park 公园 Si…

STM32——看门狗

STM32——看门狗 1.独立看门狗IWDG 独立看门狗介绍 什么是看门狗&#xff1f; 在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#x…

kubekey网页版安装k8s集群操作流程

kubekey可以一键拉起k8s集群并完成kubesphere的部署&#xff0c;以后kubekey简称kk。kk 3.2版本以前都是在宿主机上完成对应的创建集群、添加节点、升级等操作的&#xff0c;3.2版本后开始往页面操作的方向演进&#xff0c;kk 3.2版本现在还是alpha&#xff0c;所以不推荐在生产…

Unity3d Shader篇(一)— 顶点漫反射着色器解析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、顶点漫反射着色器是什么&#xff1f;1. 顶点漫反射着色器的工作原理 二、编写顶点漫反射着色器1. 定义属性2. 创建 SubShader3. 编写着色器程序段4. 完成顶…

面试宝典之深谈JVM

面试宝典之深谈JVM 1.为什么需要JVM&#xff0c;不要JVM可以吗&#xff1f; 1.JVM可以帮助我们屏蔽底层的操作系统 一次编译&#xff0c;到处运行 2.JVM可以运行Class文件 2.JDK&#xff0c;JRE以及JVM的关系 3.我们的编译器到底干了什么事&#xff1f; 仅仅是将我们的 .ja…

coreldraw怎么添加箭头?

使用coreldraw的时候知道箭头在哪里添加吗&#xff1f;下面小编就给大家带来coreldraw箭头添加教程&#xff0c;有需要的小伙伴不要错过哦。 coreldraw添加箭头方法 1、首先选择桌面Coreldraw格式图片。 2、然后点击文件夹按钮打开文件。 3、最后点击上方工具横线&#xff0c…

2024最新版TypeScript安装使用指南

2024最新版TypeScript安装使用指南 Installation and Development Guide to the Latest TypeScript in 2024 By JacksonML 1. 什么是TypeScript? TypeScript is JavaScript with syntax for types. – typescriptlang.org TypeScript 是 JavaScript 的一个超集&#xff0c;…

WPS WORD 宏导出高亮文本

WPS手机版可以直接导出高亮文本&#xff0c;但只能导出手机编辑的部分&#xff0c;如果同时在电脑上编辑过&#xff0c;电脑上高亮的无法导出&#xff0c;因为作者不一样。 但WPS电脑版没有这个功能&#xff0c;只能通过宏编程实现。 这里利用了审阅模式&#xff0c;在文字高亮…

Cesium 展示——加载 glb 格式的数据

文章目录 需求分析需求 加载渲染 glb 格式的数据, 并实现模型上的点击事件 分析 模型加载// 加载模型const position = new Cesium.Cartesian3.fromDegrees(118.29355875458516,39.51516823255016

Flink 流式读取 Debezium CDC 数据写入 Hudi 表无法处理 -D / Delete 消息

问题场景是&#xff1a;使用 Kafka Connect 的 Debezium MySQL Source Connector 将 MySQL 的 CDC 数据 &#xff08;Avro 格式&#xff09;接入到 Kafka 之后&#xff0c;通过 Flink 读取并解析这些 CDC 数据&#xff0c;然后以流式方式写入到 Hudi 表中&#xff0c;测试中发现…

HarmonyOS4.0系统性深入开发31创建列表(List)

创建列表&#xff08;List&#xff09; 概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用…

贰[2],Xamarin生成APK

1&#xff0c;生成改为Release版本 2&#xff0c;选中****.Android项目 3&#xff0c;点击生成&#xff0c;选择存档 4&#xff0c;点击分发 5&#xff0c;选择临时 6&#xff0c;添加签名标识 7&#xff0c;选择对应的签名标识&#xff0c;点击另存为

el-table动态合并

废话就不多说了&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; 合并行 // 方法一 <template><div class"container"><el-table :data"dataSource" :border"true":header-cell-style"{ font-weight: normal,…

数学建模-退火算法和遗传算法

退火算法和遗传算法 一&#xff0e;退火算法 退火算法Matlab程序如下&#xff1a; [W]xlsread(D:100个目标经度纬度);>> x[W(:,1)];>> y[W(:,2)];>> w[x y];;d1[70, 40];>> w[d1;w;d1]ww*pi/180;%角度化成弧度dzeros(102);%距离矩阵初始化for i1:101…

新手不会Git也能玩Github吗?

新手不会Git也能玩Github吗&#xff1f; 前言使用Github的准备步骤使用一种访问外网资源的方法&#xff08;这一步才是新手最容易&#xff09;注册账号 创建一个自己的仓库创建完仓库后的界面 搜索你想要的代码类型以搜索坦克大战为例以下载烟花代码为例 总结 前言 说到Github&…

kafka入门学习

kafka官网&#xff1a; Apache Kafka Index of /34 kafka学习视频&#xff1a; 05_尚硅谷_Kafka_概述_基础架构_哔哩哔哩_bilibili 学习资料&#xff1a; &#xff08;1&#xff09;【万字长文】浅谈Apache Kafka --- 入门须知 https://km.woa.com/articles/show/516284…